Spoiler:我目前正在开发一个开源聊天应用程序.
通过将操作与中间件分离,甚至将套接字客户端与中间件分离,可以更好地实现这一点.因此,结果是这样的:
- Types -> REQUEST, SUCCESS, FAILURE types for every request (not mandatory).
- Reducer -> to store different states
- Actions -> send actions to connect / disconnect / emit / listen.
- Middleware -> to treat your actions, and pass or not the current action to the socket client
- Client -> socket client (socket.io).
The code below is taken from the real app which is under development (sometimes slightly edited), and they are enough for the majority of situations, but certain stuff like the SocketClient might not be 100% complete.
Actions
你希望行动尽可能简单,因为它们经常是重复的工作,你可能最终会有很多.
export function send(chatId, content) {
const message = { chatId, content };
return {
type: 'socket',
types: [SEND, SEND_SUCCESS, SEND_FAIL],
promise: (socket) => socket.emit('SendMessage', message),
}
}
请注意,socket是一个参数化函数,这样我们就可以在整个应用程序中共享同一个socket实例,而不必担心任何导入(稍后我们将演示如何实现).
Middleware (socketMiddleware.js):
我们将使用与erikras/react-redux-universal-hot-example次使用类似的策略,不过对于socket而不是AJAX.
我们的套接字中间件将只负责处理套接字请求.
中间件将操作传递到socket客户端,并分派:
- 请求(动作
types[0]
):正在请求(action.type
被发送到减速机).
- 成功(操作
types[1]
):请求成功(action.type
和服务器响应as action.result
被发送到reducer).
- 失败(操作
types[2]
):请求失败(action.type
和服务器响应as action.error
被发送到reducer).
export default function socketMiddleware(socket) {
// Socket param is the client. We'll show how to set this up later.
return ({dispatch, getState}) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
/*
* Socket middleware usage.
* promise: (socket) => socket.emit('MESSAGE', 'hello world!')
* type: always 'socket'
* types: [REQUEST, SUCCESS, FAILURE]
*/
const { promise, type, types, ...rest } = action;
if (type !== 'socket' || !promise) {
// Move on! Not a socket request or a badly formed one.
return next(action);
}
const [REQUEST, SUCCESS, FAILURE] = types;
next({...rest, type: REQUEST});
return promise(socket)
.then((result) => {
return next({...rest, result, type: SUCCESS });
})
.catch((error) => {
return next({...rest, error, type: FAILURE });
})
};
}
SocketClient.js
唯一一个将加载和管理套接字的.io客户端.
[optional] (see 1 below in the code).关于套接字的一个非常有趣的特性.io是指您可以有message acknowledgements条回复,这是执行HTTP请求时的典型回复.我们可以使用它们来验证每个请求是否正确.请注意,为了使用此功能,服务器套接字.io命令也必须有这个最新的确认参数.
import io from 'socket.io-client';
// Example conf. You can move this to your config file.
const host = 'http://localhost:3000';
const socketPath = '/api/socket.io';
export default class socketAPI {
socket;
connect() {
this.socket = io.connect(host, { path: socketPath });
return new Promise((resolve, reject) => {
this.socket.on('connect', () => resolve());
this.socket.on('connect_error', (error) => reject(error));
});
}
disconnect() {
return new Promise((resolve) => {
this.socket.disconnect(() => {
this.socket = null;
resolve();
});
});
}
emit(event, data) {
return new Promise((resolve, reject) => {
if (!this.socket) return reject('No socket connection.');
return this.socket.emit(event, data, (response) => {
// Response is the optional callback that you can use with socket.io in every request. See 1 above.
if (response.error) {
console.error(response.error);
return reject(response.error);
}
return resolve();
});
});
}
on(event, fun) {
// No promise is needed here, but we're expecting one in the middleware.
return new Promise((resolve, reject) => {
if (!this.socket) return reject('No socket connection.');
this.socket.on(event, fun);
resolve();
});
}
}
app.js
在应用程序启动时,我们初始化SocketClient
并将其传递给store 配置.
const socketClient = new SocketClient();
const store = configureStore(initialState, socketClient, apiClient);
configureStore.js
我们将socketMiddleware
和新初始化的SocketClient
一起添加到store 中间件中(还记得我们告诉过您稍后将解释的参数吗?).
export default function configureStore(initialState, socketClient, apiClient) {
const loggerMiddleware = createLogger();
const middleware = [
...
socketMiddleware(socketClient),
...
];
[Nothing special] Action types constants
没什么特别的=你通常会做的事.
const SEND = 'redux/message/SEND';
const SEND_SUCCESS = 'redux/message/SEND_SUCCESS';
const SEND_FAIL = 'redux/message/SEND_FAIL';
[Nothing special] Reducer
export default function reducer(state = {}, action = {}) {
switch(action.type) {
case SEND: {
return {
...state,
isSending: true,
};
}
default: {
return state;
}
}
}
这看起来可能需要做很多工作,但一旦你设置好了,这是值得的.您的相关代码将更易于阅读、调试,并且不太容易出错.
PS:您也可以通过AJAX API调用遵循这一策略.