简体   繁体   中英

React Hooks: setState functionality on re-renders

This is a question regarding a possible performance hit while using hooks. Quoting useState example from react docs:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </>
  );
}

React guarantees that setState function identity is stable and won't change on re-renders. This is why it's safe to omit from the useEffect or useCallback dependency list.

I have two queries regarding the usage of useState :

  • What does identity is stable and won't change on re-renders mean ?
  • I can see that for each button an anonymous function is passed as event handler. Even if setState identity is stable as claimed by React is true, wouldn't the anonymous function be re-created at every re-render ?

Wouldn't it be more efficient if useCallback was used to define memoized functions and use them as event handlers ?

What does identity is stable and won't change on re-renders mean ?

The function returned by useState will not change across render cycles. That is, the dispatch function returned on the first render cycle can still be called after say, the 10th render cycle to set state. This allows you to setup a hook using the dispatch function at any point without needing to refresh the hook when the function reference changes.

  useEffect(() => {
    setCount(initialCount);
  }, [ initialCount, setCount ]); // <--- setCount is not needed here

I can see that for each button an anonymous function is passed as event handler. Even if setState identity is stable as claimed by React is true, wouldn't the anonymous function be re-created at every re-render ?

Yes it is true that the arrow functions will be rebuilt on re-render. The alternative is to use useCallback to memoize a callback, but at what cost? The cost of invoking useCallback , the cost of memoizing that callback, the cost of building references to it and the cost of retrieving that callback on every re-render heavily outweighs the benefits of simply building a function on every re-render.

The useCallback function itself is 20 lines long with 3 other nested function calls to other internal React APIs. All that to prevent making a single line function on every render? The math simply does not add up in favour of useCallback . The only useful scenario is when you want your callbacks to have a "stable identity" either by reference or some other mechanism, so that you can pass the callback as props without causing excessive re-renders.

Consider this component as an illustration for question 1.

function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);

  // after each render we record the value of setCount
  const ref = useRef(null);
  useEffect(() => {
    ref.current = setCount;
  }, [setCount]);

  return (
    <>
      <div>
        Did setCount change from since render?{" "}
        {(!Object.is(ref.current, setCount)).toString()}
      </div>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </>
  );
}
  1. It means during different component renders useState will be the same (Object.is)

  2. Yes anonymous functions will be re-created on every render

Wouldn't it be more efficient if useCallback was used to define memoized functions and use them as event handlers?

In this particular case, no, because useCallback does not come for free while buttons will be rendered anyway. But when we have a very heavy component to render useCallback will prevent it from unnecessary re-renders

  1. What does identity is stable and won't change on re-renders mean ?

your setCount method is stable and won't change on re-renders.

  1. I can see that for each button an anonymous function is passed as event handler. Even if setState identity is stable as claimed by React is true, wouldn't the anonymous function be re-created at every re-render ?

You can but here there is no need to do so as you are not passing it to child component, you should use useCallback if you are passing it to another React component.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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