In the React project, which is currently moving from the class component to the function component, the hooks feature greatly improves the reusability of the entire code logic. However, beginners who touch hooks without knowing the features of the respective apis will also be left with a number of hooks that take time and effort to optimize.

The devil is in the details. Let’s take a look at two common examples to see where performance is going to go wrong and how to optimize it incrementally.

Component state management is chaotic

const ShowUp = () = > {
    const [tabVisible, setTabVisible] = useState(false);
    const [dataSource, setDataSource] = useState([]);
		const [countdown, setCountdown] = useState(0)...const countdownHandler = () = > {
      if (countdown === 10) return
    	setTimeout(() = > {
      	setCountdown(countdown + 1)
        countdownHandler()
      }, 1000)
    }
    
    useEffect(() = > {
      if (tabVisible) {
        countdownHandler()
      }
    }, [tabVisible])
    
    return (
    	<Tab visible={tabVisible}>
      	{dataSource}
      </Tab>)};Copy the code

Analysis of the case

This code style is common when new users first touch hooks, where each state of a component is initialized with useState, as in class components where all variables are placed in state.

The business logic in this case is to open TAB for 10 seconds and then close it. The tabVisible state of the dataSource is associated with the view layer, but the useState layer is not necessarily bound to countdown because the view layer is set every second. This rendering of the page is unnecessary. There is also a closure trap in the code above, as countdownHandler is called with an internal countdown that is always 0, and the recursion never stops.

Suit the remedy to the case

Countdown (countdown) : Countdown (countdown) : countdown (countdown) : countdown (countdown) : countdown (countdown) : countdown (countdown) : countdown (countdown) : countdown (countdown)

setCountdown(oldCountdown= > oldCountdown + 1)
Copy the code

Countdown does not countdown to 10, because the value of countdown is cached by the closure while countdown is equal to 10. This is where our useRef comes in. The useRef can be used as the this context of the class component, and the view layer is not updated when a variable defined by the useRef is updated, just like the this variable in the class component.

const countdown = useRef(0)

const countdownHandler = () = > {
  if (countdown.current === 10) return
  setTimeout(() = > {
		countdown.current += 1
    countdownHandler()
  }, 1000)}Copy the code

Of course, the actual business scenario might not countdown, just setTimeout set to 10 seconds, but for a scenario like countdown, there is a better choice.

Props independent rendering

const Parent = () = > {
  const [visible, setVisible] = useState(false);
  const [level, setLevel] = useState(1);
  
	return (
  	<>
    	<Child1 visible={visible} />
    	<Child2 level={level} />
    </>)}Copy the code

Case analysis

If a call to setVisible updates visible, does Child2 update visible?

Answer: If Child2 is not optimized internally, it will definitely be updated

However, this is not the result we want. Changing the state randomly will cause all the child nodes to be updated, which will cost a lot of performance. However, don’t worry too much, because react actually performs diff algorithm comparison, and the rendering time of some components with simple processing logic is almost negligible. At the same time, the React Reconciler has improved the stack Reconciler from 15 to fiber Reconciler, or fiber for short, in iterations of the version, which allows for updated views to have a scheduler to determine which actions are executed first, ensuring smooth interactions between views rather than holding up the JS main procedure. The page looks like it’s stuck. Components that are computationally intensive need to think about how to optimize.

Suit the remedy to the case

This is the easiest way to optimize the life cycle of a class component by inheriting PureComponent. For functional components, use the React. Memo API to optimize the life cycle. It’s kind of like HOC.

const Child2 = React.memo((props) = >{...return (
  	<div>.</div>)})Copy the code

After doing the first optimization, we can do the second optimization inside the Child2 component. For example, if only one of the properties of props causes a lot of computations, then we can cache the properties of that part of the component. If those properties do not change, the computations are skipped. UseMemo and useCallback are used to cache expensive computations.

The document

const Child2 = React.memo((props) = > {
  
  const realInfo = useMemo(() = > {
  	return computeExpensiveValue(props.a, props.b)
  }, [props.a, props.b])
  
	return (
  	<div>.</div>)})Copy the code

The computeExpensiveValue function is called only when props. A and props. B are changed.

conclusion

These two scenarios cover most of the hooks tuning, but there are other nice fixes for React tuning, which we’ll talk about later.