正确使用 React hooks + WebSockets [英] Proper way of using React hooks + WebSockets
问题描述
我需要连接到 WebSockets 服务器并记录它的消息.使用 React 类组件,我会将这个逻辑放在 componentDidMount
生命周期钩子中并愉快地继续,但我不确定如何使用钩子正确实现它.
这是我的第一次尝试.
import React, {useEffect} from 'react';导出默认函数 AppWs() {useEffect(() => {让 ws = new WebSocket('wss://ws.kraken.com/');ws.onopen = () =>console.log('ws 打开');ws.onclose = () =>console.log('ws 关闭');ws.onmessage = e =>{const message = JSON.parse(e.data);console.log('e', message);};返回 () =>{ws.close();}}, []);返回 (<div>钩子 + ws</div>)}
我向 useEffect
添加了连接和日志逻辑,提供了带有依赖项的空数组,一切都运行良好.直到我需要添加 pause
状态来暂停日志记录.
导出默认函数 AppWs() {const [isPaused, setPause] = useState(false);useEffect(() => {让 ws = new WebSocket('wss://ws.kraken.com/');ws.onopen = () =>console.log('ws 打开');ws.onclose = () =>console.log('ws 关闭');ws.onmessage = e =>{如果(isPaused)返回;const message = JSON.parse(e.data);console.log('e', message);};返回 () =>{ws.close();}}, []);返回 (<div><button onClick={() =>setPause(!isPaused)}>{isPaused ?'继续':'暂停'}
)}
ESLint 开始对我大喊大叫,我应该添加 isPaused
状态作为对 useEffect
的依赖.
好吧,完成了.
但是我注意到每次单击按钮后都会重新连接到 WS 服务器.这显然不是我想要的.
我的下一次迭代是使用两个 useEffect
:一个用于连接,一个用于消息处理.
导出默认函数 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 打开');setWs(wsClient);};wsClient.onclose = () =>console.log('ws 关闭');返回 () =>{wsClient.close();}}, []);useEffect(() => {如果(!ws)返回;ws.onmessage = e =>{如果(isPaused)返回;const message = JSON.parse(e.data);console.log('e', message);};}, [isPaused, ws]);返回 (<div><button onClick={() =>setPause(!isPaused)}>{isPaused ?'继续':'暂停'}
)}
这按预期工作,但我感觉我错过了一些东西,可以更轻松地解决此任务,只需一个 useEffect
.请帮助重构代码,让我相信我正在以正确的方式使用 React 钩子.谢谢!
由于您只设置一次 web socket,我认为更好的方法是使用 ref 而不是状态:
useEffect
的顺序很重要.
正如 George 在评论中所建议的,在第一个 useEffect
ws.current
被保存到一个变量中以确保当 close
被称为它指的是同一个实例.
导出默认函数 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 打开");ws.current.onclose = () =>console.log(ws 关闭");const wsCurrent = ws.current;返回 () =>{wsCurrent.close();};}, []);useEffect(() => {如果(!ws.current)返回;ws.current.onmessage = e =>{如果(isPaused)返回;const message = JSON.parse(e.data);控制台日志(e",消息);};}, [isPaused]);返回 (<div><button onClick={() =>setPause(!isPaused)}>{是暂停?简历":暂停"}按钮>
);}
I need to connect to WebSockets server and log it's messages. With React class component I'd put this logic in componentDidMount
lifecycle hook and move on happily, but I'm not sure how to properly implement it with hooks.
Here's my first attempt.
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>
)
}
I added connection and log logic to useEffect
, provided empty array with dependencies, and everything worked great. Until I needed to add pause
state to pause logging.
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 started to yell at me that I should add isPaused
state as a dependency to useEffect
.
Well, ok, done.
But I noticed re-connection to WS server after every time I click the button. This is clearly not what I want.
My next iteration was to use two useEffect
s: one for connection and one for message processing.
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>
)
}
This works as expected, but I have a feeling that I miss something and this task can be solved easier, with one useEffect
.
Please help to refactor the code on convince me that I'm using React hooks in a proper way. Thanks!
As you are only setting the web socket once, I think a better approach is to use a ref instead of a state:
The order of useEffect
is important.
As suggested by George in the comments, in the first useEffect
ws.current
is saved to a variable to make sure that when close
is called it refers to the same instance.
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>
);
}
这篇关于正确使用 React hooks + WebSockets的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!