我想与服务器建立Web套接字连接。如果连接关闭5秒钟后重新连接。我正在使用React Hooks,到目前为止已经实现了
import React, { useRef, useState, useEffect } from 'react';`
import './App.css';
function App() {
const wsClient = useRef(null);
const [wsState, setWsState] = useState(true)
useEffect(() => {
wsClient.current = new WebSocket(url);
console.log("Trying to open ws");
setWsState(true)
wsClient.current.onopen = () => {
console.log('ws opened');
wsClient.current.send('{"type" : "hello"}')
};
wsClient.current.onclose = (event) => {
// Parse event code and log
setTimeout(() => {setWsState(false)}, 5000)
console.log('ws closed');
}
wsClient.current.onmessage = ((event) => {
// DO YOUR JOB
})
return () => {
console.log('ws closed');
wsClient.current.close();
}
}, [wsState]);
return (
<div className="App">
<Header />
<MainBody />
</div>
);
}
当它无法与服务器连接时,这将导致重试次数成倍增加,如果我删除setTimeout
并使用简单的方法,setState
它可以正常工作。我无法理解问题,也无法提出实现目标的最佳实践。
我不认为效果是最好的选择。如果是应用程序级的,则在其自己的模块中实现它可能更简单,并在需要时将其引入。
尽管如此,要使其正常工作,您应该考虑管理两个独立的生命周期:组件生命周期和websocket生命周期。要使其按需工作,必须确保一个状态中的每个状态更改与另一个状态中的状态更改保持一致。
首先,请记住,每次更改数组中的依赖关系时,效果都会运行。因此,在您的示例中,每次设置时,效果都会运行wsState
。
要记住的另一件事是,每次wsState
更改时都会调用清理函数,效果是执行两次(将其设置为true
“打开”和false
“关闭”)。这意味着,当您创建一个新的套接字而无法连接时,将触发close事件,并将状态更改排入队列。
每次尝试连接时,它都会设置wsState
为true
(将重新播放效果排队),尝试连接并失败,最后设置另一个超时,将状态更新为false
。但是,在效果再次运行之前,请尝试将状态设置为true等。
要解决此问题,请从效果生命周期开始。您的效果什么时候开始?什么时候应该清理?一些想法:
这对组件意味着什么?您不想将WS状态包括为依赖项。但是,您确实需要状态来触发它以在超时后重新运行。
看起来是这样的:
import React, { useRef, useState, useEffect } from 'react';
const URL = 'ws://localhost:8888';
export default function App() {
const clientRef = useRef(null);
const [waitingToReconnect, setWaitingToReconnect] = useState(null);
const [messages, setMessages] = useState([]);
const [isOpen, setIsOpen] = useState(false);
function addMessage(message) {
setMessages([...messages, message]);
}
useEffect(() => {
if (waitingToReconnect) {
return;
}
// Only set up the websocket once
if (!clientRef.current) {
const client = new WebSocket(URL);
clientRef.current = client;
window.client = client;
client.onerror = (e) => console.error(e);
client.onopen = () => {
setIsOpen(true);
console.log('ws opened');
client.send('ping');
};
client.onclose = () => {
if (clientRef.current) {
// Connection failed
console.log('ws closed by server');
} else {
// Cleanup initiated from app side, can return here, to not attempt a reconnect
console.log('ws closed by app component unmount');
return;
}
if (waitingToReconnect) {
return;
};
// Parse event code and log
setIsOpen(false);
console.log('ws closed');
// Setting this will trigger a re-run of the effect,
// cleaning up the current websocket, but not setting
// up a new one right away
setWaitingToReconnect(true);
// This will trigger another re-run, and because it is false,
// the socket will be set up again
setTimeout(() => setWaitingToReconnect(null), 5000);
};
client.onmessage = message => {
console.log('received message', message);
addMessage(`received '${message.data}'`);
};
return () => {
console.log('Cleanup');
// Dereference, so it will set up next time
clientRef.current = null;
client.close();
}
}
}, [waitingToReconnect]);
return (
<div>
<h1>Websocket {isOpen ? 'Connected' : 'Disconnected'}</h1>
{waitingToReconnect && <p>Reconnecting momentarily...</p>}
{messages.map(m => <p>{JSON.stringify(m, null, 2)}</p>)}
</div>
);
}
在此示例中,将跟踪连接状态,但不会跟踪useEffect依赖项。waitingForReconnect
是的。并在关闭连接时设置,并在一段时间后取消设置以触发重新连接尝试。
清理也会触发关闭,因此我们需要onClose
通过查看客户端是否已被取消引用来区分。
如您所见,这种方法相当复杂,并且将WS生命周期与组件生命周期联系在一起(从技术角度来说,如果您是在应用程序级别执行此操作,则可以)。
但是,一个主要警告是,过时的关闭确实很容易遇到问题。例如,addMessage
可以访问局部变量消息,但是由于addMessage
没有作为依赖项传递,因此每次运行效果时都不能两次调用它,否则它将覆盖最后一条消息。(本质上,这不是覆盖,而是用旧的,“陈旧”的值messages
与新值相连接来覆盖状态。调用它十次,您只会看到最后一个值。)
因此,您可以添加addMessage
依赖项,但随后您将断开并重新连接每个渲染的websocket。您可以摆脱addMessages
,而只需将该逻辑移入效果中,但是每次更新messages
数组时,它都会重新运行(频率比每个渲染器都低,但仍然非常频繁)。
因此,我建议您在应用程序生命周期之外设置您的客户端。您可以使用自定义钩子来处理传入消息,也可以直接在效果中直接处理它们。
这是一个例子:
import React, { useRef, useState, useEffect } from 'react';
const URL = 'ws://localhost:8888';
function reconnectingSocket(url) {
let client;
let isConnected = false;
let reconnectOnClose = true;
let messageListeners = [];
let stateChangeListeners = [];
function on(fn) {
messageListeners.push(fn);
}
function off(fn) {
messageListeners = messageListeners.filter(l => l !== fn);
}
function onStateChange(fn) {
stateChangeListeners.push(fn);
return () => {
stateChangeListeners = stateChangeListeners.filter(l => l !== fn);
};
}
function start() {
client = new WebSocket(URL);
client.onopen = () => {
isConnected = true;
stateChangeListeners.forEach(fn => fn(true));
}
const close = client.close;
// Close without reconnecting;
client.close = () => {
reconnectOnClose = false;
close.call(client);
}
client.onmessage = (event) => {
messageListeners.forEach(fn => fn(event.data));
}
client.onerror = (e) => console.error(e);
client.onclose = () => {
isConnected = false;
stateChangeListeners.forEach(fn => fn(false));
if (!reconnectOnClose) {
console.log('ws closed by app');
return;
}
console.log('ws closed by server');
setTimeout(start, 3000);
}
}
start();
return {
on,
off,
onStateChange,
close: () => client.close(),
getClient: () => client,
isConnected: () => isConnected,
};
}
const client = reconnectingSocket(URL);
function useMessages() {
const [messages, setMessages] = useState([]);
useEffect(() => {
function handleMessage(message) {
setMessages([...messages, message]);
}
client.on(handleMessage);
return () => client.off(handleMessage);
}, [messages, setMessages]);
return messages;
}
export default function App() {
const [message, setMessage] = useState('');
const messages = useMessages();
const [isConnected, setIsConnected] = useState(client.isConnected());
useEffect(() => {
return client.onStateChange(setIsConnected);
}, [setIsConnected]);
useEffect(() => {
if (isConnected) {
client.getClient().send('hi');
}
}, [isConnected]);
function sendMessage(e) {
e.preventDefault();
client.getClient().send(message);
setMessage('');
}
return (
<div>
<h1>Websocket {isConnected ? 'Connected' : 'Disconnected'}</h1>
<form onSubmit={sendMessage}>
<input value={message} onChange={e => setMessage(e.target.value)} />
<button type="submit">Send</button>
</form>
{messages.map(m => <p>{JSON.stringify(m, null, 2)}</p>)}
</div>
);
}
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句