Component PureRender

Performance optimizations in class components can be shouldComponentUpdate or inherited from PureComponent. ShouldComponentUpdate is also shouldComponentUpdate. Internally shallowEqual state and props.

There is no lifecycle for function components to call, so the only way to optimize performance is through react.Memo (
), which is the same as inheriting PureComponent.

Alternatively, if your function component needs to get its ref, you can use the following utility functions:

function memoForwardRef<N.P> (comp: RefForwardingComponent<N, P>) {
  return memo(forwardRef<N, P>(comp));
}
Copy the code

But it’s not just that the performance is done, you also need to make sure that the props and internal state references don’t change unexpectedly.

Locally invariant

For functional components, variable references are an important concern, whether functions or objects.

const Child = React.memo(({ columns }) = > {
  return <Table columns={columns} />
})
const Parent = () => {
  const data = [];
  return <Child columns={data} />
}
Copy the code

In the case of the above components, the columns references are changed every time the Parent renders them, although the contents of the columns do not change. The performance optimization fails even when react. memo is used when props is passed to the Child.

In this case, the reference can be stored via useMemo and the dependency invariant reference will not change.

const data = useMemo((a)= > [], [])
Copy the code

UseMemo scenarios are mostly used for value calculation. For example, in a computationally intensive scenario you don’t want to re-render a component without changing the dependency and repeatedly executing the calculation function to get the same value.

For functions, you can use useCallback if you want to save a reference to it.

function Counter() {
  const [count, setCount] = useState(0)


  // Write the function so that a new function is created again each time you re-render
  const onIncrement = (a)= > {
    setCount(count= > count + 1)}const onIncrement = useCallback((a)= > {
    setCount(count= > count + 1)}, [])return (
    <div>
      <button onClick={onIncrement}>INCREMENT</button>
      <p>{count}</p>
    </div>)}Copy the code

For the above code, the component’s onIncrement reference that uses the useCallback wrapper does not change every time it renders, which means it does not need to create and destroy functions as often as possible.

However, in the case of useCallback dependencies, function references do not always stay the same as you would expect, as in the following example:

function Counter() {
  const [count, setCount] = useState(0)

  const onIncrement = useCallback((a)= > {
    setCount(count= > count + 1)}, [])const onLog = useCallback((a)= > {
    console.log(count)
  }, [count])

  return (
    <div>
      <button onClick={onIncrement}>INCREMENT</button>
      <button onClick={onLog}>Log</button>
      <p>{count}</p>
    </div>)}Copy the code

The onLog function is recreated each time a change in count causes the component to be re-rendered. There are two general methods to keep function references unchanged in this case.

  1. useuseEventCallback
  2. useuseReducer
function useEventCallback(fn, dependencies) {
  const ref = useRef((a)= > {
    throw new Error('Cannot call an event handler while rendering.');
  });

  useEffect((a)= > {
    ref.current = fn;
  }, [fn, ...dependencies]);

  return useCallback((a)= > {
    const fn = ref.current;
    return fn();
  }, [ref]);
}
Copy the code

UseEventCallback uses the ref invariant feature to ensure that the reference to the callback function is never changed. In addition, in Hooks, dispatch is also fixed, so changing the dependency on ref to DISPATCH and calling dispatch in the callback is another option.

Performance tuning is not a silver bullet

There are two sides to every coin. By introducing the above performance optimizations, you have already reduced the original performance. After all, they come with a usage cost.

function updateCallback(callback, deps) {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if(prevState ! = =null) {
    if(nextDeps ! = =null) {
      const prevDeps = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

function updateMemo(nextCreate, deps) {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if(prevState ! = =null) {
    if(nextDeps ! = =null) {
      const prevDeps = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0]; }}}const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}
Copy the code

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

It can be seen that shallowEqual is basically used in the performance optimization schemes mentioned in this paper to compare the differences before and after, so there is no need to optimize for performance optimization.

The pit of Hooks

99% of the Hooks bugs are caused by closures, let’s look at an example of where closures can cause problems.

function App() {
  const [state, setState] = React.useState(0)
  // What do you think the answer will be?
  const handleClick = (a)= > {
    setState(state + 1)
    setTimeout((a)= > {
      console.log(state)
    }, 2000)}return (
    <>
      <div>{state}</div>
      <button onClick={handleClick} />
    </>
  )
}
Copy the code

What do you think the answer will be after the above code triggers handleClick three times? The answer may not be what you think, but the result is:

0 1 2

Since each render has a new state, the use of setTimeout in the above code generates a closure that captures the state after each render, resulting in output 0, 1, 2.

If you want the output to be the latest state, you can use useRef to save the state. As mentioned earlier, the ref only has one copy in the component, and references to it do not change whenever they are used, thus solving problems caused by closures.

function App() {
  const [state, setState] = React.useState(0)
  // use ref
  const currentState = React.useRef(state)
  // Update the values after each rendering
  useEffect((a)= > {
    currentState.current = state
  })

  const handleClick = (a)= > {
    setState(state + 1)
    // Get the latest value from ref
    setTimeout((a)= > {
      console.log(currentState.current)
    }, 2000)}return (
    <>
      <div>{state}</div>
      <button onClick={handleClick} />
    </>
  )
}
Copy the code

The problem with closures is that they keep old values, so you just have to get the latest values and basically solve the problem.

Write in the last

If you think I’m missing something or writing something wrong, feel free to point it out.

I’d love to hear what you think. Thanks for reading.

Wechat scan code to follow the public account, subscribe to more exciting content Add the author wechat group chat technology