Performance optimization of React functional components

Optimization ideas

There are two main optimization directions:

  1. Reduce the number of re-renders. Because the heaviest (and the longest) piece in React is the reconction (simply understood as diff), if it is not rendered, there will be no reconction.
  2. Reduce the amount of computation. The main purpose is to reduce repeated calculations. For functional components, each render will re-execute the function call from scratch.

When using class components, the React optimization APIs used are mainly: shouldComponentUpdate and PureComponent

So in functional components, how do we do performance optimization? Mainly use the following methods to optimize

  1. React.memo
  2. useCallback
  3. useMemo

React.memo

See an example:

We put a button in the parent component to modify the subtitle and introduce the Child subcomponent

As you can see, the first time you come in, the subcomponent prints console.log('I am a subcomponent')

When clicking to modify the subtitle, the Child subcomponent is also printed, causing unnecessary repeated rendering times

//父组件
import {useState} from 'react'

import Child from "./Child";
const Index = ()=>{
    const [subTitle, setSubTitle] = useState('我是子标题')
    const updateSubTitle = ()=>{
      setSubTitle('修改子标题')
    }
    return (
      <div>
        <div>函数式组件性能优化</div>
        <div>{subTitle}</div>
        <button onClick={updateSubTitle}>修改子标题</button>
        <Child/>
      </div>
    );
  }
  
  export default Index;


//子组件Child.js
const Child = ()=>{
    console.log('我是子组件')
    return (
        <div>我是子组件</div>
    )
}
export default Child
复制代码

Optimize it, use React.memo to wrap child components

import React from "react";

const Child = ()=>{
    console.log('我是子组件')
    return (
        <div>我是子组件</div>
    )
}
export default React.memo(Child)
复制代码

Observe again and find that the Child subcomponent is not rendered repeatedly.

useCallback

Here we remodel it again, add an onclick event to the Child sub-component, and then click the Modify sub-title button, and find that our Child sub-component is re-rendered. This is mainly because the handlerClick function re-renders changes when modifying the sub-title, causing the child component re-render

// 父组件
const Index = ()=>{
    const [subTitle, setSubTitle] = useState('我是子标题')
    const updateSubTitle = ()=>{
      setSubTitle('修改子标题')
    }
    const handlerClick = ()=>{
      console.log('子组件点击')
    }
    return (
      <div>
        <div>函数式组件性能优化</div>
        <div>{subTitle}</div>
        <button onClick={updateSubTitle}>修改子标题</button>
        <Child onClick={handlerClick}/>
      </div>
    );
  }

// Child子组件
const Child = (props)=>{
    console.log('我是子组件')
    return (
        <div>
            <div>我是子组件</div>
            <button onClick={props.onClick}>子组件按钮</button>
        </div>
    )
}
export default React.memo(Child)
复制代码

Optimize it, use useCallback to wrap the handlerClick function of the sub-component, click updateSubTitle again to modify the sub-title, and find that the Child sub-component is not re-rendered

// 父组件
const Index = ()=>{
    const [subTitle, setSubTitle] = useState('我是子标题')
    const updateSubTitle = ()=>{
      setSubTitle('修改子标题')
    }
    const handlerClick = useCallback(()=>{
      console.log('子组件点击')
    },[])

    return (
      <div>
        <div>函数式组件性能优化</div>
        <div>{subTitle}</div>
        <button onClick={updateSubTitle}>修改子标题</button>
        <Child onClick={handlerClick}/>
      </div>
    );
  }
  
  export default Index;
复制代码

Here's the usage of useCallback

const callback = () => {
  doSomething(a, b);
}

const memoizedCallback = useCallback(callback, [a, b])
复制代码

Pass the function and dependencies as parameters to useCallback, and it will return a memoized version of the callback function. This memoizedCallback will only be updated when the dependencies change.

useMemo

useMemo for calculation result caching

我们先看个例子,在之前基础上添加一个calcCount计算函数,然后点击updateSubTitle更新子标题,发现calcCount重新计算了,也就是每次渲染都会造成重复计算,如果是计算量比较大的情况下,会极大的影响性能

// 父组件
const Index = ()=>{
    const [subTitle, setSubTitle] = useState('我是子标题')
    const updateSubTitle = ()=>{
      setSubTitle('修改子标题')
    }
    const handlerClick = useCallback(()=>{
      console.log('子组件点击')
    },[])

    const calcCount = ()=>{
      
      let totalCount = 0
      for(let i=0;i<10000;i++){
        totalCount+=i
      }
      console.log('totalCount',totalCount)
      return totalCount
    }

    const count = calcCount()

    return (
      <div>
        <div>函数式组件性能优化</div>
        <div>{subTitle}</div>
        <button onClick={updateSubTitle}>修改子标题</button>
        <div>count:{count}</div>
        <Child onClick={handlerClick}/>
      </div>
    );
  }
复制代码

优化一下,使用useMemo缓存计算结果,我们再次点击updateSubTitle修改子标题按钮,可以发现calcCount函数不再重复计算

  const calcCount = ()=>{
      
      let totalCount = 0
      for(let i=0;i<10000;i++){
        totalCount+=i
      }
      console.log('totalCount',totalCount)
      return totalCount
    }

    const count = useMemo(calcCount,[])
复制代码

最后,需要注意的是不能盲目的使用useMemo,要根据具体的场景,比如对于一个数据计算量比较大,那么使用是比较适用的,而对于普通的一些值得计算,可以不使用,因为本身useMemo也是会消耗一些性能,盲目使用反而会适得其反

参考阅读

文章最后

本文作者阿健Kerry,高级前端工程师,转载请注明出处。如果觉得本文对你有帮助,记得点赞三连哦,也可以扫码关注我新建立的前端技术公众号【有你前端】,之后我所有文章会同步发到这个公众号上面。另外,我建了一个可以帮助咱们程序员脱单的公众号,每周都会推送几个优秀的单身小姐姐,如果你是程序员技术好又正好是单身,那你可以下面扫码关注【缘来你是程序猿】公众号开启你的脱单之旅。

Guess you like

Origin juejin.im/post/6954595499261296654