Esto crea una aplicación de chat de demostración que involucra salas de socket.io integradas mediante enlaces React. El cliente puede conectarse y alternar entre salas de chat, donde las actualizaciones serán replicadas por eventos socket.io. Dependiendo de la sala seleccionada, transmitirá mensajes de chat a los clientes de socket que usen esa sala.
(Una vez que se ejecuta, podemos activar varias pestañas del navegador y ver si los mensajes de chat se actualizan a través de sockets).
Creamos un servidor de nodo con una configuración simple de socket.io
var http = require('http').createServer().listen(3000);
var io = require('socket.io').listen(http);
io.on('connection', (socket) => {
console.log(`Connected: ${socket.id}`);
socket.on('disconnect', () =>
console.log(`Disconnected: ${socket.id}`));
socket.on('join', (room) => {
console.log(`Socket ${socket.id} joining ${room}`);
socket.join(room);
});
socket.on('chat', (data) => {
const { message, room } = data;
console.log(`msg: ${message}, room: ${room}`);
io.to(room).emit('chat', message);
});
});
Aquí es necesario instalar el paquete npm socket.io y el servidor de socket se crea en el mismo puerto 3000 que con el servidor http.
Instalamos el socket.io-client paquete y creemos funciones relacionadas con el socket para tratar con el servidor de socket y eso se usará con los hooks de React.
import io from 'socket.io-client';
let socket;
export const initiateSocket = (room) => {
socket = io('http://localhost:3000');
console.log(`Connecting socket...`);
if (socket && room) socket.emit('join', room);
}
export const disconnectSocket = () => {
console.log('Disconnecting socket...');
if(socket) socket.disconnect();
}
export const subscribeToChat = (cb) => {
if (!socket) return(true);
socket.on('chat', msg => {
console.log('Websocket event received!');
return cb(null, msg);
});
}
export const sendMessage = (room, message) => {
if (socket) socket.emit('chat', { message, room });
}
Estas funciones de socket se pueden usar en la aplicación React fácilmente con ganchos. Con useState hooks mantendrá sus variables para room (seleccionado), message (mensaje actual) y chat (todos los mensajes).
import React, { useEffect, useState } from 'react';
import { initiateSocket, disconnectSocket,
subscribeToChat, sendMessage } from './Socket';
function App() {
const rooms = ['A', 'B', 'C'];
const [room, setRoom] = useState(rooms[0]);
const [message, setMessage] = useState('');
const [chat, setChat] = useState([]);
useEffect(() => {
if (room) initiateSocket(room);
subscribeToChat((err, data) => {
if(err) return;
setChat(oldChats =>[data, ...oldChats])
});
return () => {
disconnectSocket();
}
}, [room]);
return (
<div>
<h1>Room: {room}</h1>
{ rooms.map((r, i) =>
<button onClick={() => setRoom(r)} key={i}>{r}</button>)}
<h1>Live Chat:</h1>
<input type="text" name="name" value={message}
onChange={e => setMessage(e.target.value)} />
<button onClick={()=> sendMessage(room,message)}>Send</button>
{ chat.map((m,i) => <p key={i}>{m}</p>) }
</div>
);
}
export default App;
Aquí se utiliza el gancho más importante useEffect utilizado para la interacción de socket (o cualquier otra llamada externa) .
subscribeToChat al cambiar de socket.Este enfoque simple hace que las salas de chat funcionen en las que varios clientes que utilizan pestañas del navegador pueden interactuar dentro de sus salas de conexión. Sin embargo, existen pocas preocupaciones cuando se trata de escenarios avanzados.
Cada vez que se emitían mensajes de chat, se requería enviar lo que se room pretendía, aunque el socket del cliente se unió a una sala específica antes. Esto se debe al hecho de que socket puede tener varias salas (una por su propia identificación) y se puede almacenar en un servidor en formato socket.rooms . La sala se pasa cada vez para asegurarse de que se transmitirá al grupo de enchufes correcto.
io.on("connection", (socket) => {
let socketRoom;
socket.on('join', (room) => {
socket.join(room);
socketRoom = room;
});
socket.on('chat', (data) => {
io.to(socketRoom).emit('chat', data.message);
});
});
En el servidor, socket puede mantener información relevante ya sea en el alcance de socket individual o en el alcance global incluido con una identificación única. Esto también puede almacenar cosas individuales como el nombre de usuario, la sala de chat adjunta y demás. Tenga en cuenta que esto debe mantenerse al conectarse, desconectarse o actualizarse y requiere asistencia adicional.
En el ámbito del socket local:
const socketMap = {};
io.on("connection", (socket) => {
socket.on('join', (data) => {
socket.join(data.room);
socketMap[socket.id] = data.username;
});
socket.on('chat', (data) => {
io.to(data.room).emit('chat',
{ message: data.message, user: socketMap[socket.id] });
});
});
Un inconveniente importante aquí es que al cambiar entre habitaciones, se desconectará el enchufe en la habitación anterior y hará una nueva conexión en la habitación más nueva. Esto agregará una sobrecarga no deseada al cambiar de habitación, pero en su lugar, se puede usar un enchufe conectado existente para cambiar entre habitaciones.
Socket Q9_wYGZtDJVjf7MWAAAI joining A
Disconnected: Q9_wYGZtDJVjf7MWAAAI
Connected: eWn2OGaXaml-0eS6AAAJ
Socket eWn2OGaXaml-0eS6AAAJ joining B
Modificar App.js para cambiar entre habitaciones usando useRef & useEffect hooks:
// Let's keep track of previous room
const prevRoomRef = useRef();
useEffect(() => {
prevRoomRef.current = room;
});
const prevRoom = prevRoomRef.current;
// Initiate or Switch Rooms depending on previous and current values
useEffect(() => {
if (prevRoom && room) switchRooms(prevRoom, room);
else if (room) initiateSocket(room);
setChat([]);
// Reset chat messages upon change in room
// Avoid subscribeToChat as it will duplicate subscriptions.
// Avoid disconnectSocket as cleanup as socket is reused.
}, [room]);
// Subscribe only once to event as socket is reused
useEffect(() => {
subscribeToChat((err, data) => {
if(err) return;
setChat(oldChats =>[data, ...oldChats])
});
}, []);
Emitir switch evento Socket.js con salas relevantes:
export const switchRooms = (prevRoom, nextRoom) => {
if (socket) socket.emit('switch', { prevRoom, nextRoom });
}
Emitir switch evento Socket.js con salas relevantes:
socket.on('switch', (data) => {
const { prevRoom, nextRoom } = data;
if (prevRoom) socket.leave(prevRoom);
if (nextRoom) socket.join(nextRoom);
socketRoom = nextRoom;
});
En algunos escenarios, la aplicación en tiempo real puede agregar un mensaje de chat localmente antes de esperar la actualización del socket. Para este enfoque, es mejor si ese mensaje no se recibe al remitente a través de la suscripción de socket, y esto requiere que el mensaje no se transmita al remitente en esa sala.
const socketHistory = {};
io.on("connection", (socket) => {
let socketRoom;
socket.on('join', (room) => {
socket.join(room);
socketRoom = room;
socket.emit('joinResponse', socketHistory[room]);
});
socket.on('chat', (data) => {
socket.broadcast.to(socketRoom).emit('chat', data.message);
socketHistory[socketRoom] = socketHistory[socketRoom] ?
[data.message, ...socketHistory[socketRoom]] : [data.message]
});
En la interfaz, suscríbase una vez al joinResponse evento para que cargue los datos iniciales
// Socket.js
export const loadInitialChat = (cb) => {
if (!socket) return(true)
socket.on('joinResponse', msg => cb(null, msg));
}
// App.js
useEffect(() => {
loadInitialChat((err, data) => {
if(err) return;
setChat(data);
});
}, []);
Este es un tutorial de demostración sobre cómo integrarse fácilmente con salas de socket.io con hooks react js.