• React函数式组件的性能优化


    优化思路

    主要优化的方向有2个:

    1. 减少重新 render 的次数。因为在 React 里最重(花时间最长)的一块就是 reconction(简单的可以理解为 diff),如果不 render,就不会 reconction。
    2. 减少计算的量。主要是减少重复计算,对于函数式组件来说,每次 render 都会重新从头开始执行函数调用。

    在使用类组件的时候,使用的 React 优化 API 主要是:shouldComponentUpdate和 PureComponent

    那么在函数式组件中,我们怎么做性能优化?主要用到下面几个方法去优化

    1. React.memo
    2. useCallback
    3. useMemo

    React.memo

    看个例子:

    我们在父组件中放一个按钮用于修改子标题,并引入Child子组件

    可以看到,第一次进来子组件打印了console.log('我是子组件')

    当点击修改子标题,Child子组件也打印了,造成了不必要的重复渲染次数

    //父组件
    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
    

    优化一下,使用React.memo包裹子组件

    import React from "react";
    
    const Child = ()=>{
        console.log('我是子组件')
        return (
            <div>我是子组件</div>
        )
    }
    export default React.memo(Child)
    

    再观察一下,发现Child子组件没有重复渲染了

    useCallback

    这里我们再改造一下,给Child子组件添加一个onclick事件,然后点击修改子标题按钮,发现我们的Child子组件又重新渲染了,这里主要是因为修改子标题的时候handlerClick函数重新渲染变化,造成子组件重新渲染

    // 父组件
    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)
    

    优化一下,使用useCallback包裹处理子组件的handlerClick函数,再次点击updateSubTitle修改子标题,发现Child子组件没有重新再渲染

    // 父组件
    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;
    

    这里关于useCallback的用法

    const callback = () => {
      doSomething(a, b);
    }
    
    const memoizedCallback = useCallback(callback, [a, b])
    

    把函数以及依赖项作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,这个 memoizedCallback 只有在依赖项有变化的时候才会更新。

    useMemo

    useMemo用于计算结果缓存

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

    作者:fozero
    文章出处:https://www.cnblogs.com/fozero
    声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    设计模式_2_简单工厂、工厂方法、抽象工厂比较
    SQL模拟padding函数
    MySqlHelper c#访问MySql的工具类
    常见数据库设计(1)——字典数据
    常见数据库设计(2)——历史数据问题之单记录变更
    设计模式_1_单例模式
    代码调用存储过程超时,SQL Server Management Studio里运行很快 (改进)
    转:Rowid和Rownum区别
    Oracle数据库中system和sys的区别
    转:Python之全局变量
  • 原文地址:https://www.cnblogs.com/fozero/p/14696733.html
Copyright © 2020-2023  润新知