【问题标题】:How to use context values in useEffect, that only runs once如何在useEffect中使用上下文值,只运行一次
【发布时间】: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()

【问题讨论】:

    标签: reactjs react-hooks use-effect use-context


    【解决方案1】:

    拆分 useEffect 的解决方案

    您可以将打开 websocket 连接的逻辑与添加消息处理程序的逻辑拆分为单独的 useEffects - 第一个可以运行一次,而第二个可以在每次依赖项更改时重新附加事件:

    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) =&gt; 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
      }, [])
      

      【讨论】:

        猜你喜欢
        • 2022-12-16
        • 2021-06-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多