React Hooks实战_性能优化

本文探讨了React Hooks在实际项目中可能遇到的性能问题,包括组件状态管理混乱和Props无关渲染。针对这些问题,文章提供了优化方案,如使用useRef解决闭包陷阱,以及利用React.memo和useMemo优化组件渲染。最后,作者总结了这两个场景的性能优化,并提到React性能优化的其他方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在做react项目的迁移,从class组件写法迁移至function组件,不得不说hooks的特性对整个代码逻辑复用性的提高还是很不错的。但是新手在接触hooks的时候,如果不了解各个API的特性的话,同样也会埋下许多神坑,后期优化费时费力。

要解决性能问题,关键在于对组件重复渲染的处理,魔鬼在细节,下面就来通过两个常见的案例来分析性能到底会毁在哪几个细节,并且渐进式地给出优化方案。

组件状态管理混乱

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>
    )
};
复制代码

分析案例

这种代码风格在新手刚开始接触hooks的时候会很常见,把组件的各个状态都用useState进行初始化,类比class组件就是把所有的变量都放在state里。

这个案例中的业务逻辑是想要打开tab的10秒后关闭,然后状态中的tabVisible,dataSource是和视图层有关系的,但是countdown缺没必要绑定useState,因为countdown倒计时的时候,每一秒都会设置一次视图层,这样对页面的渲染就是不必要。同时,上面的代码中还存在闭包陷阱,countdownHandler方法在调用的时候,内部的countdown永远都是0,这个递归永远无法停止下来。

对症下药

我们先看闭包问题怎么解决,闭包问题的关键在于如何获取最新的countdown值,这时候就可以改变下我们setState的调用形式,改成传入回调函数的方式来更新值。

setCountdown(oldCountdown => oldCountdown + 1)
复制代码

其实这样做还是没有解决问题,因为在判断countdown是否等于10的时候,countdown值还是被闭包缓存下来了,所以递归还是跳不出来。 这时候就要我们的useRef登场了,useRef可以拿来当class组件的this上下文来用,而且用useRef定义的变量更新的时候并不会更新视图层,这个特性和class组件里的this变量相同。

const countdown = useRef(0)

const countdownHandler = () => {
  if (countdown.current === 10) return
  setTimeout(() => {
        countdown.current += 1
    countdownHandler()
  }, 1000)
}
复制代码

当然实际业务场景的话可能不会写countdown,直接setTimeout设置10秒就好了,但是针对这种countdown类似的场景,就有更好的选择了。

Props无关渲染

const Parent = () => {
  const [visible, setVisible] = useState(false);
  const [level, setLevel] = useState(1);
  
    return (
    <>
        <Child1 visible={visible} />
        <Child2 level={level} />
    </>
  )
}
复制代码

案例分析

分析一下上面的伪代码,假如调用setVisible更新了visible,Child2组件是否会更新呢?

答案:如果Child2内部没做优化,那么肯定会更新

但这并不是我们想要的结果,随便改个状态就导致所有子节点都更新的话,那就非常耗费性能了,不过也不用太过于担心,因为react内部其实会有做diff算法比较,一些处理逻辑简单的组件的渲染耗时几乎可以忽略不计。同时,react在版本的迭代中把15的stack reconciler改进成了16的fiber reconciler,简称fiber,这使得更新视图的时候,有一个调度器来决定哪些行为会优先执行,保证视图交互顺畅,而不是一直占用js主程,导致页面看起来卡死了一样。密集计算逻辑的组件的话就要思考一下怎么去优化优化。

对症下药

这种场景其实考虑一下class组件的做法,我们会选择继承PureComponent的方式来优化生命周期,这是最简单一种方式,如果是函数式组件的话,就要用React.memo这个api来优化,使用起来也很简单,在组件外层包裹一层即可,有点像HOC。

const Child2 = React.memo((props) => {
  ...
    return (
    <div>
        ...
    </div>
  )
})
复制代码

做了第一步优化后,我们可以继续对Child2组件内部做第二步优化,比如说每次props只有部分属性会导致大量计算,那么我们就能对这一部分的属性进行缓存,如果这部分属性不变的话,那么就会跳过计算,hooks就有这样的API,useMemo和useCallback就是用来缓存高开销的计算。

文档

const Child2 = React.memo((props) => {
  
  const realInfo = useMemo(() => {
    return computeExpensiveValue(props.a, props.b)
  }, [props.a, props.b])
  
    return (
    <div>
        ...
    </div>
  )
})
复制代码

这里只会在props.a和props.b变化时,才进行computeExpensiveValue函数的调用,否则,realInfo一直会用上一次计算后缓存下来的值,这样的话,虽然props有更新,但是也并不会完全执行全部业务逻辑。

总结

这两个场景基本能涵盖到大部分的hooks性能优化,不过react性能优化的话还有别的很不错的方案,之后我们有机会再来聊聊。

感谢阅读

博主在此给大家安利一款分享学习资料的app,此app的主要就是收集了互联网上的各种学习资料做整合并免费分享。里面涵盖了Android、java、python、数据结构、算法、前端、爬虫、大数据、深度学习、UI等等技术,包含M课网课程、某课堂VIP课程、各培训机构课程、某鱼的一些付费课程以及算法面试资料,并且正在不断更新中,你想要的学习资料几乎都有,当然你也可以加入我们的技术Q群获取各种资料:687667883,任何课程问题可直接咨询群主。
目前对接的课程论坛如下,您可以提前了解下有哪些课程:
链接资源如下(下面论坛所有的课程,app中均有并且是免费的,如果出现未录入的情况,加上面的Q群找群主):
    享学app资源对接论坛一(IT码上发)
    享学app资源对接论坛二(学途无忧)

目前app已经在小米、华为应用市场上架,可搜索“享学”即可下载,其他市场正在同步上架中,同时你也可扫码以下二维码进行下载:
下载二维码_1.0.0版本.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值