【问题标题】:React component create a new instance of the state when re-rendering even the state is not changedReact 组件在重新渲染时创建一个新的状态实例,即使状态没有改变
【发布时间】:2020-04-25 14:37:44
【问题描述】:

我将Socket.IO 与React.js 一起使用,并且我希望websocket 仅在呈现特定组件时启动,因此我使用websocket 作为该组件的状态,如下所示:

const Comp = () => {
  const [ws] = useState(socketIO());
  // ... the rest of the component ...
};

但是当Comp 重新渲染时,它会创建新的 websocket 连接,即使我没有对连接进行任何更改。过了一会儿,我最终得到了 10 多个 websocket 连接。如何使组件仅保持 1 个连接?我不希望 websocket 连接成为全球性的。

【问题讨论】:

    标签: javascript reactjs websocket socket.io


    【解决方案1】:

    请记住,函数式组件只是函数,所有关于函数的常见知识都适用。尽管钩子在幕后做了一些表面上的魔术(这并不是真正的魔术,只是它们具有我们看不到的上下文信息),但组件函数中的代码按照通常的规则运行。这意味着这段代码

    const Comp = () => {
      const [ws] = useState(socketIO());
      // ... the rest of the component ...
    };
    

    将总是调用socketIO,以便将其返回值传递给useState。

    要计算一个值一次,当组件第一次创建时,使用 ref (useRef) 来表示非状态实例信息(你可以直接存储在一个类组件实例),像这样:

    const Comp = () => {
      const {current: instance} = useRef({});
      const ws = instance.ws = instance.ws || socketIO();
      // ... the rest of the component ...
    };
    

    {} 仍会在每次调用 Comp 时进行评估,但这种开销是微不足道的。你从useRef 得到的是一个具有current 属性的对象,该属性指的是一个可变对象——在组件的整个生命周期中始终保持不变。第二行使用该对象的ws 属性,如果它是虚假的,则第一次初始化它。

    useRef docs 中提到了这种用法:

    不过,useRef() 的用处不止是 ref 属性。它可以方便地保留任何可变值,类似于在类中使用实例字段的方式。

    下面是socketIO 的替代示例:

    const { useRef, useState } = React;
    
    function socketIO() {
      console.log("socketIO called");
      return {};
    }
    
    const Comp = () => {
      console.log("Comp called");
      const {current: instance} = useRef({});
      const ws = instance.ws = instance.ws || socketIO();
      const [counter, setCounter] = useState(0);
      
      return (
        <div>
          {counter} <input type="button" value="+" onClick={() => setCounter(c => c + 1)} />
        </div>
      );
    };
    
    ReactDOM.render(<Comp/>, document.getElementById("root"));
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

    我应该注意到,在这里使用useMemo 很诱人,但是useMemo 并不能保证您构建记忆结果的函数不会被第二次调用。 useMemo 用于性能优化,而不是语义。来自the docs:

    你可以依赖 useMemo 作为性能优化,而不是语义保证。 未来,React 可能会选择“忘记”一些以前记忆的值并在下次渲染时重新计算它们,例如为屏幕外组件释放内存。编写您的代码,使其在没有 useMemo 的情况下仍然可以工作 - 然后添加它以优化性能。

    (他们的重点)

    【讨论】:

    • 感谢您的详细解答。但是请您向我解释一下这里的链接分配:const ws = instance.ws = instance.ws || socketIO();?
    • @TríPhan - 该表达式的工作方式如下:它计算 instance.ws || socketIO(),这意味着 || 运算符计算左侧操作数 (instance.ws),如果结果为 truthy (除null、undefined、0、NaN、"" 或 false 之外的任何值),它会将该值作为结果。如果左侧运算符的值为 falsy(任何这些值),它会计算右侧(对 socketIO 的调用)并将该结果作为其结果。然后完成第一个赋值(instance.ws = x,其中x 是第一个表达式的结果)。结果……
    • ...赋值是被赋值的值,所以该表达式的结果也是x。然后ws = x 部分就完成了。所以第一次创建组件并运行Comp,因为instance 将是一个空白对象,instance.ws 将是undefined 和socketIO 将被调用;结果将分配给instance.ws 和ws。下一次调用 Comp 时,instance.ws 已经有一个真实值,所以 socketIO 不会被调用,ws 会得到这个值。
    猜你喜欢
    • 1970-01-01
    • 2019-09-23
    • 2020-11-05
    • 2020-10-24
    • 2020-06-18
    • 2020-02-18
    • 2019-08-08
    • 2020-09-30
    • 2020-11-30
    相关资源
    最近更新 更多