如何在useEffect中使用上下文值,只运行一次
Posted
技术标签:
【中文标题】如何在useEffect中使用上下文值,只运行一次【英文标题】:How to use context values in useEffect, that only runs once 【发布时间】:2021-12-26 04:11:05 【问题描述】:我在这里遇到了一个有趣的问题。我正在使用与服务器的 Web 套接字通信构建一个反应应用程序。我在 useEffect 钩子中创建了这个 websocket,因此它不能运行多次,否则我最终会得到多个连接。但是,在这个 useEffect 中,我打算使用一些变量,它们实际上位于上下文 (useContext) 挂钩中。并且当上下文值发生变化时, useEffect 中的值不会更新,这是可以理解的。我试过useRef,但没有用。你有什么想法吗?
const ws = useRef<WebSocket>();
useEffect(() =>
ws.current = new WebSocket("ws://localhost:5000");
ws.current.addEventListener("open", () =>
console.log("opened connection");
);
ws.current.addEventListener("message", (message) =>
const messageData: ResponseData = JSON.parse(message.data);
const response, reload = messageData;
if (typeof response === "string")
const event = new CustomEvent<ResponseData>(response,
detail: messageData,
);
ws.current?.dispatchEvent(event);
else
if (reload !== undefined)
console.log("general info should reload now");
GeneralInfoContext.reload(reload);
console.log(messageData);
);
);
Web 套接字存储为 ref,以便在此 useEffect 块之外的不同功能中更好地使用
注意:要使用的上下文值实际上是一个函数,GeneralInfoContext.reload()
【问题讨论】:
【参考方案1】:拆分 useEffect 的解决方案
您可以将打开 websocket 连接的逻辑与添加消息处理程序的逻辑拆分为单独的 useEffect
s - 第一个可以运行一次,而第二个可以在每次依赖项更改时重新附加事件:
useEffect(() =>
ws.current = new WebSocket("ws://localhost:5000");
ws.current.addEventListener("open", () =>
console.log("opened connection");
);
, []);
useEffect(() =>
const socket = ws.current;
if(!socket) throw new Error("Expected to have a websocket instance");
const handler = (message) =>
/*...*/
socket.addEventListener("message", handler);
// cleanup
return () => socket.removeEventListener("message", handler);
, [/* deps here*/])
效果将按顺序运行,因此第二个效果将在第一个效果已经设置ws.current
之后运行。
带有回调参考的解决方案
或者,您可以将处理程序放入 ref 并根据需要对其进行更新,并在调用事件时引用该 ref:
const handlerRef = useRef(() => )
useEffect(() =>
handlerRef.current = (message) =>
/*...*/
// No deps here, can update the function on every render
);
useEffect(() =>
ws.current = new WebSocket("ws://localhost:5000");
ws.current.addEventListener("open", () =>
console.log("opened connection");
);
const handlerFunc = (message) => handlerRef.current(message);
ws.current.addEventListener("message", handlerFunc);
return () => ws.current.removeEventListener("message", handlerFunc);
, []);
重要的是不要执行addEventListener("message", handlerRef.current)
,因为这只会附加函数的原始版本 - 额外的(message) => handlerRef.current(message)
包装器是必要的,这样每条消息都会传递到处理函数的最新版本。
这种方法仍然需要两个useEffect
,因为最好不要将handlerRef.current = /* func */
直接放在渲染逻辑中,因为渲染不应该有副作用。
使用哪个?
我个人喜欢第一个,分离和重新附加事件处理程序应该是无害的(基本上是“免费的”)并且感觉比添加额外的 ref 更简单。
但是第二个避免了对显式依赖列表的需要,这也很好,特别是如果您不使用 eslint 规则来确保详尽的依赖项。 (虽然你绝对应该这样做)
【讨论】:
【参考方案2】:您可以向useEffect
提供变量列表,useEffect
将在这些变量更改时重新运行。
这是一个小例子:
const [exampleState, setExampleState] = useState<boolean>(false);
useEffect(() =>
console.log("exampleState was updated.");
, [exampleState]);
来自 reactjs 网站的示例:
useEffect(() =>
function handleStatusChange(status)
setIsOnline(status.isOnline);
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () =>
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
;
, [props.friend.id]); // Only re-subscribe if props.friend.id changes
【讨论】:
好吧,我尝试添加这个数组,尝试包括GeneralInfoContext,就像这样和.reload,但是这个效果仍然不会在每次更改时运行,并且我一直得到默认函数值(我知道这一点是因为我在里面放了一个控制台日志,所以我可以跟踪)【参考方案3】:您应该将一个空数组作为第二个参数传递给 useEffect,所以这种情况下它类似于 react 的 componentDidMount() 逻辑
useEffect(() =>
...your websocket code here
, [])
【讨论】:
以上是关于如何在useEffect中使用上下文值,只运行一次的主要内容,如果未能解决你的问题,请参考以下文章
如何正确使用 useEffect 与 useContext 作为依赖
如果它们的状态在 React useEffect 中发生变化,如何区分上下文值