Я начал интегрировать веб-сокеты в существующее приложение React / Django, следуя наряду с этим примером (сопровождающее репо здесь ). В этом репо интерфейс веб-сокета находится в websockets.js
и реализован в containers/Chat.js
.
. Я могу заставить этот код работать правильно, как есть.
Затем я начал переписывать свою реализацию, чтобы использовать Hooks, и ударил по маленькой стене. Данные проходят через сокет корректно, корректно поступают в обработчик каждого клиента и внутри обработчика могут прочитать правильное состояние. В этом обработчике я вызываю свою функцию useState для обновления состояния входящими данными.
Изначально у меня была проблема с моей единственной useState
функцией при addMessage()
непостоянном срабатывании (1 в 10 раз?). Я разделил один useState
хук на два (один для текущего сообщения, один для всех сообщений). Теперь в addMessage()
после получения данных с сервера мой хук setAllMessages
обновит только клиента, в который я ввожу сообщение, - никаких других клиентов. Все клиенты получают / могут правильно регистрировать данные, они просто не запускают функцию setAllMessages
.
Если я добавлю sh в пустой массив вне функции, он будет работать, как и ожидалось. Так что это похоже на проблему в цикле обновления функций, но я не смог отследить ее.
Вот моя версия websocket.js
:
class WebSocketService {
static instance = null;
static getInstance() {
if (!WebSocketService.instance) {
WebSocketService.instance = new WebSocketService();
}
return WebSocketService.instance;
}
constructor() {
this.socketRef = null;
this.callbacks = {};
}
disconnect() {
this.socketRef.close();
}
connect(chatUrl) {
const path = `${URLS.SOCKET.BASE}${URLS.SOCKET.TEST}`;
this.socketRef = new WebSocket(path);
this.socketRef.onopen = () => {
console.log('WebSocket open');
};
this.socketRef.onmessage = e => {
this.socketNewMessage(e.data);
};
this.socketRef.onerror = e => {
console.log(e.message);
};
this.socketRef.onclose = () => {
this.connect();
};
}
socketNewMessage(data) {
const parsedData = JSON.parse(data);
const { command } = parsedData;
if (Object.keys(this.callbacks).length === 0) {
return;
}
Object.keys(SOCKET_COMMANDS).forEach(clientCommand => {
if (command === SOCKET_COMMANDS[clientCommand]) {
this.callbacks[command](parsedData.presentation);
}
});
}
backend_receive_data_then_post_new(message) {
this.sendMessage({
command_for_backend: 'backend_receive_data_then_post_new',
message: message.content,
from: message.from,
});
}
sendMessage(data) {
try {
this.socketRef.send(JSON.stringify({ ...data }));
} catch (err) {
console.log(err.message);
}
}
addCallbacks(allCallbacks) {
Object.keys(SOCKET_COMMANDS).forEach(command => {
this.callbacks[SOCKET_COMMANDS[command]] = allCallbacks;
});
}
state() {
return this.socketRef.readyState;
}
}
const WebSocketInstance = WebSocketService.getInstance();
export default WebSocketInstance;
А вот моя версия Chat.js
export function Chat() {
const [allMessages, setAllMessages] = useState([]);
const [currMessage, setCurrMessage] = useState('');
function waitForSocketConnection(callback) {
setTimeout(() => {
if (WebSocketInstance.state() === 1) {
callback();
} else {
waitForSocketConnection(callback);
}
}, 100);
}
waitForSocketConnection(() => {
const allCallbacks = [addMessage];
allCallbacks.forEach(callback => {
WebSocketInstance.addCallbacks(callback);
});
});
/*
* This is the problem area
* `incoming` shows the correct data, and I have access to all state
* But `setAllMessages` only updates on the client I type the message into
*/
const addMessage = (incoming) => {
setAllMessages([incoming]);
};
// update with value from input
const messageChangeHandler = e => {
setCurrMessage(e.target.value);
};
// Send data to socket interface, then to server
const sendMessageHandler = e => {
e.preventDefault();
const messageObject = {
from: 'user',
content: currMessage,
};
setCurrMessage('');
WebSocketInstance.backend_receive_data_then_post_new(messageObject);
};
return (
<div>
// rendering stuff here
</div>
);
}