Ответ 1
Спойлер: В настоящее время я разрабатываю то, что будет чат-приложением с открытым исходным кодом.
Вы можете сделать это лучше, отделив действия от промежуточного программного обеспечения и даже клиента сокета от промежуточного программного обеспечения. Следовательно, в результате чего-то вроде этого:
- Типы → REQUEST, SUCCESS, FAILURE для каждого запроса (не обязательно).
- Редуктор → для хранения различных состояний
- Действия → отправлять действия для подключения/отключения/испускания/прослушивания.
- Middleware → для обработки ваших действий и передачи или отсутствия текущего действия клиенту сокета
- Клиент → клиент сокета (socket.io).
Код ниже берется из реального приложения, которое находится в стадии разработки (иногда слегка отредактировано), и их достаточно для большинства ситуаций, но некоторые вещи, такие как 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),
}
}
Обратите внимание, что сокет является параметризованной функцией, поэтому мы можем использовать один и тот же экземпляр сокета во всем приложении, и нам не нужно беспокоиться о каком-либо импорте (мы покажем, как это сделать позже).
Middleware (socketMiddleware.js):
Мы будем использовать аналогичную стратегию, как erikras/react-redux-universal-hot-example, хотя для сокета вместо AJAX.
Наше сокетное ПО будет отвечать за обработку только запросов сокетов.
Middleware передает действие клиенту сокета и отправляет:
- REQUEST (действие
types[0]
): запрашивает (action.type
отправляется на редуктор). - SUCCESS (действие
types[1]
): по запросу успешно (action.type
и ответ сервера какaction.result
отправляется на редуктор). - FAILURE (действие
types[2]
): по запросу отказа (action.type
и ответ сервера как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 заключается в том, что вы можете иметь подтверждения сообщений, которые будут типичными ответами при выполнении 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
и передаем его в конфигурацию магазина.
const socketClient = new SocketClient();
const store = configureStore(initialState, socketClient, apiClient);
configureStore.js
Мы добавляем socketMiddleware
с нашей вновь инициализированной SocketClient
к средним магазинам (помните тот параметр, который, как мы говорили, мы объясним позже?).
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: Вы также можете следовать этой стратегии с помощью вызовов API AJAX.