javascript - ejemplo - websocket nodejs
Arquitectura en una aplicación nativa de reacción usando WebSockets (3)
Grandes respuestas aquí hasta ahora. Solo quería agregar que donde guardas tus datos realmente debería ser una decisión basada en qué tipo de datos son. James Nelson tiene un excelente artículo sobre este tema al que me refiero regularmente.
Para su caso, hablemos de los primeros 3 tipos de estado:
- Datos
- Estado de comunicación
- Estado de control
Datos
Su conexión WebSocket es genérica y técnicamente podría devolver cualquier cosa, pero es probable que los mensajes que está recibiendo sean datos. Por ejemplo, digamos que estás construyendo una aplicación de chat. Luego, el registro de todos los mensajes que se han enviado y recibido serán los datos. Debe almacenar estos datos en redux con un reductor de messages
:
export default function messages(state = [], action) {
switch (action.type) {
case ''SEND_MESSAGE'':
case ''RECEIVE_MESSAGE'': {
return [ ...state, action.message ];
}
default: return state;
}
}
No tenemos que (y no deberíamos) tener ninguna lógica WebSocket en nuestros reductores, ya que son genéricos y no nos importa de dónde provienen los datos.
Además, tenga en cuenta que este reductor es capaz de manejar el envío y la recepción exactamente de la misma manera. Esto se debe a que la comunicación de red se maneja por separado por nuestro reductor de estado de comunicación.
Estado de comunicación
Dado que está utilizando WebSockets, los tipos de estado de comunicación que desea rastrear pueden diferir de mi ejemplo. En una aplicación que utiliza una API estándar, realizaría un seguimiento cuando una solicitud se está cargando , ha fallado o se ha realizado correctamente .
En nuestro ejemplo de aplicación de chat, es probable que desee realizar un seguimiento de estos estados de solicitud cada vez que envíe un mensaje, pero también puede haber otras cosas que desee clasificar como estado de comunicación.
Nuestro reductor de network
puede usar las mismas acciones que el reductor de messages
:
export default function network(state = {}, action) {
switch (action.type) {
case ''SEND_MESSAGE'': {
// I''m using Id as a placeholder here. You''ll want some way
// to tie your requests with success/failure receipt.
return {
...state,
[action.id]: { loading: true }
};
} case ''SEND_MESSAGE_SUCCESS'': {
return {
...state,
[action.id]: { loading: false, success: true }
};
} case ''SEND_MESSAGE_FAILURE'': {
return {
...state,
[action.id]: { loading: false, success: false }
};
}
default: return state;
}
}
De esta manera, podemos encontrar fácilmente el estado de nuestras solicitudes y no tenemos que preocuparnos por la carga / éxito / falla en nuestros componentes.
Sin embargo, es posible que no le importe el éxito o el fracaso de una solicitud determinada, ya que está utilizando WebSockets. En ese caso, su estado de comunicación podría ser solo si su socket está conectado o no. Si eso le suena mejor, simplemente escriba un reductor de connection
que responda a las acciones de abrir / cerrar.
Estado de control
También necesitaremos algo para iniciar el envío de mensajes. En el ejemplo de la aplicación de chat, este es probablemente un botón de envío que envía cualquier texto en un campo de entrada. No demostraré todo el componente, ya que usaremos un componente controlado .
El punto clave aquí es que el estado de control es el mensaje antes de enviarlo. Lo interesante del código en nuestro caso es qué hacer en handleSubmit
:
class ChatForm extends Component {
// ...
handleSubmit() {
this.props.sendMessage(this.state.message);
// also clear the form input
}
// ...
}
const mapDispatchToProps = (dispatch) => ({
// here, the `sendMessage` that we''re dispatching comes
// from our chat actions. We''ll get to that next.
sendMessage: (message) => dispatch(sendMessage(message))
});
export default connect(null, mapDispatchToProps)(ChatForm);
Entonces, eso se dirige a donde va todo nuestro estado. Hemos creado una aplicación genérica que podría utilizar acciones para fetch
una API estándar, obtener datos de una base de datos o cualquier otra fuente. En su caso, desea utilizar WebSockets
. Entonces, esa lógica debería vivir en tus acciones.
Comportamiento
Aquí, creará todos sus controladores: onOpen
, onMessage
, onError
, etc. Estos aún pueden ser bastante genéricos, ya que ya tiene su utilidad WebSocket configurada por separado.
function onMessage(e) {
return dispatch => {
// you may want to use an action creator function
// instead of creating the object inline here
dispatch({
type: ''RECEIVE_MESSAGE'',
message: e.data
});
};
}
Estoy usando thunk para la acción asíncrona aquí. Para este ejemplo en particular, puede que no sea necesario, pero es probable que tenga casos en los que desee enviar un mensaje, luego manejar el éxito / el fracaso y enviar múltiples acciones a sus reductores desde una única acción de sendMessage
. Thunk es genial para este caso.
Cableando todo junto
Finalmente, tenemos todo preparado. Todo lo que tenemos que hacer ahora es inicializar el WebSocket y configurar los oyentes apropiados. Me gusta el patrón que Vladimir sugirió - configurar el socket en un constructor - pero parametrizaría sus devoluciones de llamada para que pueda entregar sus acciones. Entonces su clase WebSocket puede configurar todos los oyentes.
Al hacer que la clase WebSocket sea un singleton , puede enviar mensajes desde sus acciones sin necesidad de administrar las referencias al socket activo. También evitarás contaminar el espacio de nombres global.
Al utilizar la configuración de singleton, cada vez que llame a new WebSocket()
por primera vez, se establecerá su conexión. Por lo tanto, si necesita que la conexión se abra tan pronto como se inicie la aplicación, la configuraría en componentDidMount
of App
. Si una conexión perezosa está bien, puede esperar hasta que su componente intente enviar un mensaje. La acción creará un nuevo WebSocket y se establecerá la conexión.
Tengo una aplicación React Native que voy a construir y que utiliza WebSockets. Tengo una biblioteca WebSocket escrita en JavaScript y simplemente la estoy reutilizando para este proyecto, lo cual es fantástico.
Mi pregunta es, siendo nuevo en React / React Native, ¿cuál es la mejor práctica para configurar y mantener todo el tráfico que pasa por WebSocket?
Inicialmente, mi idea fue crear el websocket en el componente principal de la aplicación, algo como esto:
export default class App extends Component {
constructor(props) {
super(props);
this.ws = new WebSocket;
}
componentWillMount() {
console.log(this.ws);
}
render() {
console.log("We are rendering the App component.....");
return (
<View style={styles.container}>
<Text style={styles.welcome}>Hello, world</Text>
</View>
);
}
}
La clase real de WebSocket contendría todo el manejo de la conexión respectiva:
ws.onopen = () => {
// connection opened
ws.send(''something''); // send a message
};
ws.onmessage = (e) => {
// a message was received
console.log(e.data);
};
ws.onerror = (e) => {
// an error occurred
console.log(e.message);
};
ws.onclose = (e) => {
// connection closed
console.log(e.code, e.reason);
};
Mi pregunta es, ya que los datos que vienen a través de WebSocket serán aplicables al estado a través de muchos componentes en la aplicación React Native, pero no es una clase que extienda a React.Component
, ¿no interactúo con Redux en la clase WebSocket
? ¿Muevo todo el manejo de la conexión WebSocket al componente de la App
y despacho las acciones allí a Redux?
¿Cuál es el patrón común aquí para crear una instancia de mi clase de WebSocket
y garantizar que todo el tráfico en ella se pase correctamente a Redux para que el estado de todos los componentes se canalice correctamente?
No hay pautas oficiales al respecto. Creo que usar un componente es confuso porque no se procesará, y supongo que si usas Redux, quieres compartir los datos de websocket en cualquier lugar de la aplicación.
Puede asignar la función de envío a su administrador de Websocket.
const store = createStore(reducer);
const ws = new WebSocket(store.dispatch, store.getState);
// constructor(dispatch, getState) {
// this.dispatch = dispatch;
// this.getState = getState;
// }
Y usa this.dispatch
dentro de tus métodos de clase.
También puedes usar middlewares para manejar los efectos secundarios, creo que es la forma recomendada. Hay dos grandes bibliotecas que puedes mirar:
Puedes crear una clase dedicada para WebSocket
y usarla en todas partes. Es un enfoque simple, conciso y claro. Además, tendrás todo lo relacionado con los websockets encapsulados en un solo lugar. Si lo desea, puede incluso crear singleton fuera de esta clase, pero la idea general es la siguiente:
class WS {
static init() {
this.ws = new WebSocket(''ws://localhost:5432/wss1'');
}
static onMessage(handler) {
this.ws.addEventListener(''message'', handler);
}
static sendMessage(message) {
// You can have some transformers here.
// Object to JSON or something else...
this.ws.send(message);
}
}
Solo has ejecutado init en algún lugar de index.js
o app.js
:
WS.init();
Y ahora puede enviar mensajes desde cualquier nivel de aplicación, desde cualquier componente, desde cualquier lugar:
WS.sendMessage(''My message into WebSocket.'');
Y recibe los datos de vuelta de WebSocket:
WS.onMessage((data) => {
console.log(''GOT'', data);
// or something else or use redux
dispatch({type: ''MyType'', payload: data});
});
¡Así que puedes usarlo en todas partes incluso en redux en cualquier acción o en cualquier otro lugar!