最近在做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已经在小米、华为应用市场上架,可搜索“享学”即可下载,其他市场正在同步上架中,同时你也可扫码以下二维码进行下载: