Spoiler: Ich entwickle gerade, was eine Open-Source-Chat-Anwendung sein wird.
Sie können das besser tun, indem Sie Aktionen von der Middleware und sogar dem Socket-Client von der Middleware trennen. Daher, was in etwa wie folgt:
- Typen -> REQUEST, Erfolg, Misserfolg Typen für jede Anforderung (nicht zwingend).
- Reducer -> speichern verschiedene Zustände
- Aktionen -> Aktionen senden verbinden/trennen/emittieren/hören.
- Middleware -> Ihre Aktionen zu behandeln, und übergeben oder nicht die aktuelle Aktion an den Socket-Client
- Client- -> Socket-Client (socket.io).
Der folgende Code aus der realen Anwendung genommen wird, die in der Entwicklung (manchmal leicht bearbeitet), und sie sind für die meisten Situationen genug, aber bestimmte Sachen wie das SocketClient vielleicht nicht 100% vollständig sein.
Aktionen
Sie wollen Aktionen so einfach wie möglich sein, da sie oft Arbeit wiederholt werden, und Sie werden wahrscheinlich viele von ihnen am Ende mit.
export function send(chatId, content) {
const message = { chatId, content };
return {
type: 'socket',
types: [SEND, SEND_SUCCESS, SEND_FAIL],
promise: (socket) => socket.emit('SendMessage', message),
}
}
Beachten Sie, dass Buchse ist eine parametrisierte Funktion, so können wir die gleiche Buchse Instanz in der gesamten Anwendung teilen können und müssen wir uns über jede Einfuhr keine Sorge überhaupt (wir werden zeigen, wie dies später zu tun).
Middleware (socketMiddleware.js):
Wir werden eine ähnliche Strategie wie erikras/react-redux-universal-hot-example Anwendungen verwenden, obwohl für Sockel anstelle von AJAX.
Unsere Socket-Middleware wird nur für die Verarbeitung von Socket-Anforderungen zuständig sein.
Middleware passiert die Aktion auf den Socket-Client, und entsendet:
- REQUEST (action
types[0]
): anfordert (action.type
wird Minderer gesendet).
- ERFOLG (Aktion
types[1]
): auf Anfrage Erfolg (action.type
und Serverantwort als action.result
wird an Reducer gesendet).
- FAILURE (Aktion
types[2]
): auf Anfrage Fehler (action.type
und Serverantwort als action.error
werden zum Reducer geschickt).
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
Die einzige, die jemals geladen wird und die socket.io-Client verwalten.
[optional] (siehe 1 unten im Code). Ein sehr interessantes Feature über socket.io ist die Tatsache, dass Sie message acknowledgements haben können, was die typischen Antworten bei einer HTTP-Anfrage wären. Wir können sie verwenden, um zu überprüfen, ob jede Anfrage korrekt war. Beachten Sie, dass socket.io-Befehle auch diesen neuesten Bestätigungsparameter haben müssen, um diesen Feature-Server verwenden zu können.
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
Auf unserer App-Start-up, initialisieren wir die SocketClient
und an die Speicherkonfiguration übergeben.
const socketClient = new SocketClient();
const store = configureStore(initialState, socketClient, apiClient);
configureStore.js
Wir fügen den socketMiddleware
mit unserem neu SocketClient
den Speicher Middle initialisiert (denken Sie daran, dass die Parameter, die wir Ihnen gesagt, dass wir später erklären würde?).
export default function configureStore(initialState, socketClient, apiClient) {
const loggerMiddleware = createLogger();
const middleware = [
...
socketMiddleware(socketClient),
...
];
[Nothing special] Aktionstypen Konstanten
Nothing special =, was Sie normalerweise tun würde.
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;
}
}
}
Es mag wie eine Menge Arbeit suchen, aber wenn Sie es eingerichtet haben, es lohnt sich. Ihr relevanter Code wird einfacher zu lesen und zu debuggen sein und Sie werden weniger anfällig für Fehler sein.
PS: Sie können diese Strategie auch mit AJAX API-Aufrufen verfolgen.
Im Allgemeinen ist diese Art von Frage nicht gut für stackoverflow, aber ja, Ihr Ansatz sieht vernünftig aus. Nur das, was mich als "komisch" bezeichnet, ruft nur bedingt "next (action)" auf. Normalerweise möchten Sie die nächste Middleware (Dispatcher) z. um es mit der Protokollierung oder irgendetwas anderem, das Sie dort haben, umgehen zu können. – WTK
Das sieht fair aus, aber ich würde wirklich einen Blick in die Redux-Saga-Middleware empfehlen, die es dir erlauben wird, eine bestimmte Aktion auszuführen und einen Generator dafür zu aktivieren. Von dort könnten Sie erweiterte Logik, wie mehrere Kanäle, Abbestellen, etc. einrichten. – horyd
@WTK in Bezug auf die nächste Middleware, soll ich eine andere Bedingung hinzufügen, um an die nächste Middleware zu senden? – Ganbel