简体   繁体   English

React 组件在重新渲染时创建 state 的新实例,即使 state 没有更改

[英]React component create a new instance of the state when re-rendering even the state is not changed

I'm using Socket.IO with React.js and I want the websocket to start only when a specific component is rendered, so I use the websocket as a state of that component, like this: I'm using Socket.IO with React.js and I want the websocket to start only when a specific component is rendered, so I use the websocket as a state of that component, like this:

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

But when the Comp re-rendering, it creates new websocket connection, even I don't make any changes on the connection.但是当Comp重新渲染时,它会创建新的 websocket 连接,即使我没有对连接进行任何更改。 After a while, I end up with more than 10 websocket connections.过了一会儿,我得到了超过 10 个 websocket 连接。 How can I make the component keep 1 connection only?如何使组件仅保持 1 个连接? I don't want the websocket connection to become global.我不希望 websocket 连接成为全球性的。

Remember that functional components are just functions, all the usual things you know about functions apply.请记住,功能组件只是功能,您所知道的有关功能的所有常见知识都适用。 Although hooks do some seeming-magic behind the scenes (it's not really magic, it's just they have context information we don't see), the code in your component function runs according to the usual rules.尽管钩子在幕后做了一些表面上的魔术(这并不是真正的魔术,只是它们具有我们看不到的上下文信息),但组件 function 中的代码按照通常的规则运行。 That means this code这意味着这段代码

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

will always call socketIO so it can pass the return value of it into useState .始终调用socketIO以便它可以将它的返回值传递给useState

To calculate a value only once , when the component is first created, use a ref ( useRef ) to represent non-state instance information (the kind of thing you would have stored directly on a class component instance), like this:要仅计算一次值,在首次创建组件时,使用 ref ( useRef ) 来表示非状态实例信息(您将直接存储在 class 组件实例上的那种东西),如下所示:

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

The {} is still evaluated every time Comp is called, but that overhead is trivial.每次调用Comp时仍会评估{} ,但这种开销是微不足道的。 What you get back from useRef is an object with a current property referring to a mutable object — one which will always be the same throughout the lifetime of the component.你从useRef得到的是一个 object ,它的current属性指的是一个可变对象——在组件的整个生命周期中,它总是相同的。 The second line uses the ws property of that object, initializing it the first time if it's falsy.第二行使用 object 的ws属性,如果它是虚假的,则第一次初始化它。

This usage is called out in the useRef docs :这种用法在useRef文档中被提及:

However, useRef() is useful for more than the ref attribute.但是, useRef()不仅仅对 ref 属性有用。 It's handy for keeping any mutable value around similar to how you'd use instance fields in classes.它可以方便地保留任何可变值,类似于您在类中使用实例字段的方式。

Here's an example with a stand-in for socketIO :这是一个带有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>


I should note that it's tempting to use useMemo here, but useMemo doesn't guarantee that your function to build the memoized result won't be called a second time.我应该注意到在这里使用useMemo很诱人,但是useMemo不能保证您的 function 不会被第二次调用。 useMemo is for performance optimization, not semantics. useMemo用于性能优化,而不是语义。 From the docs :文档

You may rely on useMemo as a performance optimization, not as a semantic guarantee.您可能依赖 useMemo 作为性能优化,而不是语义保证。 In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, eg to free memory for offscreen components.将来,React 可能会选择“忘记”一些先前记忆的值并在下一次渲染时重新计算它们,例如释放 memory 用于屏幕外组件。 Write your code so that it still works without useMemo — and then add it to optimize performance.编写您的代码,使其在没有useMemo的情况下仍然可以工作——然后添加它以优化性能。

(their emphasis) (他们的重点)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM