javascript - 如何避免 useEffect() 竞争条件

标签 javascript reactjs react-hooks

我对 ReactJS 非常熟悉,但刚刚开始接受 Hooks。
有时对我来说困难的是如何将命令式 API 包装到钩子(Hook)中。我感觉有时我使用了错误的方法。
例如,从实际应用程序中获取这个大大简化的示例。假设有一个全局状态 API(不能更改):

  • server.getState(name)以同步方式返回最后一个已知状态(假设是一个字符串)
  • server.on(name, callback)为该状态的更改注册一个事件处理程序
  • server.off(name, callback)取消注册该事件处理程序

  • 我想创建一个自定义 Hook useServerState(name, defaultValue)返回该状态的实时值。
    我的实现如下所示:
    function useServerState(name, defaultValue) {
    
      const [ curState, setCurState ] = useState(() => server.getState(name) || defaultValue);
    
      useEffect(
        () => {
    
          const evHandler = payload => setCurState(payload);
    
          // register on mount:
          server.on(name, evHandler);
    
          // de-register on unmount:
          return () => server.off(name, evHandler);
    
        },
        [ name ]
      );
    
      return curState;
    
    }
    
    用法:
    
    function SomeComponent() {
    
      const temperature = useServerState("temp_outside", "unknown");
    
      return <div>
        Current temperature: {temperature} degrees
      </div>;
    
    }
    
    问题
    这可行,但问题是 useEffect()似乎异步调用该函数。直到我的 useEffect() 有一些延迟处理程序运行,如 50 毫秒 或者。那段时间我的钩子(Hook)对新事件是盲目的这可能会同时出现(因为尚未注册事件处理程序),这意味着即使已更新钩子(Hook)也可能返回旧状态。
    一个(不优雅的)解决方法
    我可以通过调用 getState() 来解决这个问题再次在 useEffect() 处理程序中(换句话说,在“mount”上):
    function useServerState(name, defaultValue) {
    
      const getInitialState = () => server.getState(name) || defaultValue;
      const [ curState, setCurState ] = useState(getInitialState);
    
      useEffect(
        () => {
    
          const evHandler = payload => setCurState(payload);
    
          // register on mount:
          server.on(name, evHandler);
    
    // ----->
          // anti race condition hack:
          const nv = getInitialState();
          
          if (nv !== curState)
            setCurState(nv);
    // <-----
    
          // de-register on unmount:
          return () => server.off(name, evHandler);
    
        },
        [ name ]
      );
    
      return curState;
    
    }
    
    另一个(坏的)解决方法
    另一种解决方案可能是始终使用固定的起始值并仅在安装时读取当前状态:
    function useServerState(name, defaultValue) {
    
      const [ curState, setCurState ] = useState(null);
    
      useEffect(
        () => {
    
          const evHandler = payload => setCurState(payload);
    
          // register on mount:
          server.on(name, evHandler);
    
          // set initial state:
          setCurState(server.getState(name) || defaultValue);
    
          // de-register on unmount:
          return () => server.off(name, evHandler);
    
        },
        [ name ]
      );
    
      return curState;
    
    }
    
    这个解决方案的缺点是它总是会导致两个渲染 : 一个与 null然后是另一个具有实际状态的。这是不希望的。
    怎么提高?
    变通办法对我来说似乎很尴尬,让我想知道是否有更好的方法来做到这一点,而不涉及变通办法。我希望 React 团队已经考虑过这种情况。我错过了什么?

    最佳答案

    您的原始代码注释不反射(reflect)代码逻辑,我相信修复它就足够了:

    function useServerState(name, defaultValue) {
      const [currState, setCurState] = useState(null);
    
      // Register on MOUNT
      useEffect(() => {
        if (!currState) {
          const currServerState = server.getState(name) || defaultValue;
          setCurrState(currServerState);
        }
      }, [name]);
    
      // Handle name change
      useEffect(() => {
        if (!server.getState(name)) {
          server.on(name, setCurState);
        }
        return () => server.off(name, setCurState);
      }, [name]);
    
      return currState;
    }
    

    关于javascript - 如何避免 useEffect() 竞争条件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64024877/

    相关文章:

    javascript - Jquery数据属性改变并影响CSS

    javascript - Javascript 中的自动换行功能

    javascript - 当图像以 javascript 显示时,有没有办法获取信息?

    javascript - 如何使所有选择的元素加宽 15px?

    reactjs - 使用 React 的 useState 钩子(Hook)时输入可空状态的正确方法

    reactjs - ReactDnD,能够创建一个简单的 DragDropContext - DragSource 示例

    ios - 从剪贴板粘贴不适用于 iOS 模拟器上的 React Native

    javascript - 在 React 应用程序中使用环境变量

    javascript - 如何只允许一个事件的 useState

    javascript - 如何解决TypeError : arr[Symbol. iterator] is not a function in my React project