我是React-Redux的新技术,希望在某些实现方面对您有所帮助。
我想用套接字(socket.io)实现一个聊天应用程序。首先,用户必须注册(我在服务器端使用通行证),然后,如果注册成功,则用户必须连接到webSocket。
我认为最好的办法是对所有操作使用管道之类的中间件,并根据获取中间件的操作类型来执行不同的操作。
如果操作类型为AUTH_USER,则创建客户端-服务器连接并设置所有将来自服务器的事件。
AUTH_USER
如果将操作类型MESSAGE发送到服务器,则消息。
MESSAGE
代码段:
----- socketMiddleware.js ----
import { AUTH_USER, MESSAGE } from '../actions/types'; import * as actions from 'actions/socket-actions'; import io from 'socket.io-client'; const socket = null; export default function ({ dispatch }) { return next => action => { if(action.type == AUTH_USER) { socket = io.connect(`${location.host}`); socket.on('message', data => { store.dispatch(actions.addResponse(action.data)); }); } else if(action.type == MESSAGE && socket) { socket.emit('user-message', action.data); return next(action) } else { return next(action) } } }
------ index.js -------
import {createStore, applyMiddleware} from 'redux'; import socketMiddleware from './socketMiddleware'; const createStoreWithMiddleware = applyMiddleware( socketMiddleware )(createStore); const store = createStoreWithMiddleware(reducer); <Provider store={store}> <App /> </Provider>
您如何看待这种做法,这是一种更好的实施方法吗?
Spoiler: 我目前正在开发一个开源聊天应用程序。
通过将操作与中间件分离,甚至将套接字客户端与中间件分离,您可以做得更好。因此,结果如下:
下面的代码取材于正在开发的真实应用程序(有时需要进行少量编辑),它们足以应付大多数情况,但某些东西(例如SocketClient)可能未必100%完整。
动作
您希望动作尽可能简单,因为它们通常是重复的工作,并且您最终可能会拥有很多动作。
export function send(chatId, content) { const message = { chatId, content }; return { type: 'socket', types: [SEND, SEND_SUCCESS, SEND_FAIL], promise: (socket) => socket.emit('SendMessage', message), } }
注意,套接字是一个参数化函数,通过这种方式,我们可以在整个应用程序中共享相同的套接字实例,而不必担心任何导入(稍后将说明如何执行此操作)。
中间件(socketMiddleware.js):
我们将使用与erikras / react-redux-universal-hot- example示例类似的策略,但是将它用于套接字而不是AJAX。
我们的套接字中间件将仅负责处理套接字请求。
中间件将操作传递到套接字客户端,并调度:
types[0]
action.type
types[1]
action.result
types[2]
action.error
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
唯一可以加载和管理socket.io-client的程序。
[可选](请参阅代码下面的1)。 关于socket.io的一个非常有趣的功能是您可以得到消息确认,这是在执行HTTP请求时的典型答复。我们可以使用它们来验证每个请求是否正确。请注意,为了利用此功能,服务器socket.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并将其传递到商店配置。
SocketClient
const socketClient = new SocketClient(); const store = configureStore(initialState, socketClient, apiClient);
configureStore.js
我们将socketMiddleware带有新初始化的的添加SocketClient到商店中间件(还记得我们告诉您的参数,我们稍后会解释吗?)。
socketMiddleware
export default function configureStore(initialState, socketClient, apiClient) { const loggerMiddleware = createLogger(); const middleware = [ ... socketMiddleware(socketClient), ... ];
[没什么特别的]动作类型常量
没什么特别的=您通常会做什么。
const SEND = 'redux/message/SEND'; const SEND_SUCCESS = 'redux/message/SEND_SUCCESS'; const SEND_FAIL = 'redux/message/SEND_FAIL';
[没什么特别的]减速机
export default function reducer(state = {}, action = {}) { switch(action.type) { case SEND: { return { ...state, isSending: true, }; } default: { return state; } } }
它可能看起来像很多工作,但是一旦完成设置,就值得了。您的相关代码将更易于阅读,调试,并且不易出错。
PS: 您也可以通过AJAX API调用遵循此策略。