我需要连接到WebSockets服务器并记录它的消息。使用 React 类组件,我会将此逻辑放在生命周期钩子componentDidMount
并愉快地继续前进,但我不确定如何使用钩子正确实现它。
这是我的第一次尝试。
import React, {useEffect} from 'react';
export default function AppWs() {
useEffect(() => {
let ws = new WebSocket('wss://ws.kraken.com/');
ws.onopen = () => console.log('ws opened');
ws.onclose = () => console.log('ws closed');
ws.onmessage = e => {
const message = JSON.parse(e.data);
console.log('e', message);
};
return () => {
ws.close();
}
}, []);
return (
<div>hooks + ws</div>
)
}
我在useEffect
中添加了连接和日志逻辑,提供了带有依赖项的空数组,一切正常。直到我需要添加pause
状态来暂停日志记录。
export default function AppWs() {
const [isPaused, setPause] = useState(false);
useEffect(() => {
let ws = new WebSocket('wss://ws.kraken.com/');
ws.onopen = () => console.log('ws opened');
ws.onclose = () => console.log('ws closed');
ws.onmessage = e => {
if (isPaused) return;
const message = JSON.parse(e.data);
console.log('e', message);
};
return () => {
ws.close();
}
}, []);
return (
<div>
<button onClick={() => setPause(!isPaused)}>{isPaused ? 'Resume' : 'Pause'}</button>
</div>
)
}
ESLint 开始对我大喊大叫,我应该isPaused
状态作为依赖项添加到useEffect
。
好吧,好的,完成了。
但是我注意到每次单击按钮后都会重新连接到WS服务器。这显然不是我想要的。
我的下一个迭代是使用两个useEffect
:一个用于连接,一个用于消息处理。
export default function AppWs() {
const [isPaused, setPause] = useState(false);
const [ws, setWs] = useState(null);
useEffect(() => {
const wsClient = new WebSocket('wss://ws.kraken.com/');
wsClient.onopen = () => {
console.log('ws opened');
setWs(wsClient);
};
wsClient.onclose = () => console.log('ws closed');
return () => {
wsClient.close();
}
}, []);
useEffect(() => {
if (!ws) return;
ws.onmessage = e => {
if (isPaused) return;
const message = JSON.parse(e.data);
console.log('e', message);
};
}, [isPaused, ws]);
return (
<div>
<button onClick={() => setPause(!isPaused)}>{isPaused ? 'Resume' : 'Pause'}</button>
</div>
)
}
这按预期工作,但我有一种感觉,我错过了一些东西,这个任务可以更容易地解决,只需一个useEffect
. 请帮助重构代码,让我相信我正在以正确的方式使用 React 钩子。谢谢!
由于您只设置一次 Web 套接字,我认为更好的方法是使用 ref 而不是状态:
useEffect
的顺序很重要。
正如 George 在注释中建议的那样,在第一个useEffect
ws.current
中保存到变量中,以确保在调用close
时它引用相同的实例。
export default function AppWs() {
const [isPaused, setPause] = useState(false);
const ws = useRef(null);
useEffect(() => {
ws.current = new WebSocket("wss://ws.kraken.com/");
ws.current.onopen = () => console.log("ws opened");
ws.current.onclose = () => console.log("ws closed");
const wsCurrent = ws.current;
return () => {
wsCurrent.close();
};
}, []);
useEffect(() => {
if (!ws.current) return;
ws.current.onmessage = e => {
if (isPaused) return;
const message = JSON.parse(e.data);
console.log("e", message);
};
}, [isPaused]);
return (
<div>
<button onClick={() => setPause(!isPaused)}>
{isPaused ? "Resume" : "Pause"}
</button>
</div>
);
}
useEffect(() => {
const socket = new WebSocket('wss://ws.kraken.com');
socket.addEventListener('message', function (event) {
const a = JSON.parse(event.data);
setPriceWebSocket(a);
const amin = socket.send(
JSON.stringify({
event: 'subscribe',
pair: ['XBT/USD', 'XBT/EUR', 'ADA/USD'],
subscription: { name: 'ticker' },
}),
);
});
我们都知道 react 在状态和重新渲染方面是反应性的,我们不想在我们的应用程序不断监听某些事件时弄乱当前连接,我看到的大多数答案都是把它放在他们使用useRef
处理 ws 连接的useEffect
上,这是警告, 每次组件状态更改时,它都会重新渲染组件,同时您的 WebSocket 连接也会搞砸
useEffect(()=> {
ws.current = new Websocket('URL')
ws.current.on('some-event', () => {
// console.log(state) will not get update unless we add it as dependency
})
}, [...])
有一段时间连接会频繁投射,而 React 可能会同时突然重新渲染,并且客户端连接也有可能也会投射,这取决于你的 React 重新渲染的次数,是的,后端也可能会遇到麻烦,作为 React 开发人员,我们讨厌仅仅因为一个状态更改而不必要的重新渲染。
注意:正确处理 React 应用程序的重新渲染和状态,如果你将来不会搞砸的话。
那么,我们如何解决这个问题?,请记住,我们的应用程序有一个入口文件index.js
,我们可以放在您的套接字配置和状态管理库的位置之上,所以在这里我如何正确结束它
//socket.ts
const client = new SocketClient(WEBSOCKET_SERVER_URL, {
namespace: 'chat',
messageType: 'bytes'
});
client.connect(() => console.log('SUCCESS CONNECTION'));
client.error(() => console.log('ERROR CONNECTION'));
const socketConfig = (store: Store) => {
// ... do something here
socket.on('some-event', () => {...});
};
export {socket}
export default socketConfig
//index.ts
import {createRoot} from 'react-dom/client';
import store from 'ducks'; //lets assume this is our state manager
import socketConfig from 'api/socket'
import App from './App';
const rootElement = document.getElementById('root') as Element;
const root = createRoot(rootElement);
socketConfig(store);
root.render(<App store={store} />);
//status.js
import {socket} from 'api/socket';
...
useEffect(() => {
const statusListener = () => {...}
socket.on('status-event', statusListener)
return () => {
socket.remove('status-event', statusListener);
}, [])
...
SocketClient
是基于Websocket
的工厂类,当然可以自己创建