原标题:react性能优化方法,react如何优化性能
导读:
React性能优化指南使用React开发的项目,可以从加载性能和运行时性能两个方面进行优化。加载性能优化的目标是让用户更早地看到界面、更早地和应用交互。运行时性能优化目标是降低...
React性能优化指南
使用React开发的项目,可以从加载性能和运行时性能两个方面进行优化。
加载性能优化的目标是让用户更早地看到界面、更早地和应用交互。运行时性能优化目标是降低卡顿,交互更流畅。
我们知道React的setState会触发diff和更新。默认是将整个组件树进行对比,但很多情况下diff是不必要的,因为一个子组件的props没有改变,就不需要进行diff工作。
为了避免这种对没有改变props的子组件进行多余的diff工作的情况,React提供了shouldComponentUpdate这个生命周期钩子, shouldComponentUpdate(nextProps, nextState)。这个生命周期钩子如果返回true,则会执行后面的render和diff工作,如果返回false,则React不会向下继续。用户可以在这个生命周期钩子中进行state和props的对比,判断是否需要更新。通常一个组件当前的props与nextProps属性值相同,并且state的属性值也相同,则不需要更新。
React.PureComponent实现了shouldComponentUpdate这个方法,PureComponent采用了浅比较,
【前端面试刷题网站:灵题库,收集大厂面试真题,相关知识点详细解析。】
对应class组件的PureComponent,函数组件有React.memo方法实现类似的效果。
React.memo
由于默认的PureComponent和memo都是默认用的浅比较。因此如果对象层级较深,会导致漏更新。
解决办法是,如果对象改变,重新创建一个对象,如果数组改变,重新创建一个数组,解构赋值可以很容易地实现这一点:{...oldData};[...oldArr]。
用户可以自己实现shouldComponentUpdate以自定义比较逻辑,对于函数式组件,则可以通过React.memo的第二个参数来定义比较逻辑。
如果想要精确地判断区别,除了手动判断,还有一个自动化程度比较高的方式:不可变数据,这时一个不可变数据的JS实现: immutable-js。
只有发生改动的节点会创建新的引用,因此相应的组件才会执行render和diff。
结论:最佳实践是PureComponent/React.memo+不可变数据。
Fragment可以避免不必要的dom节点。
JSX的标签表达式要求有一个根节点
如果就想让表达式返回一个标签列表,不应该在最外层加一个根节点,应该使用Fragment。
也可以简写
在注册事件回调时候,不要用匿名函数或者用bind生成新函数,应该用箭头函数或者构造里面bind,最好是构造函数里面bind(因为可以继承)。
当我们需要注册事件回调时候,可以写成这样写:
或者
上面这两种:匿名函数和bind表达式,都不推荐。因为匿名函数的写法会在每次调用render时候都创建新的函数,而bind表达式也会在每次调用时候创建一个新的函数,React做diff时候发现事件回调函数不同,就会将旧的函数解绑(这样还会触发GC)并且绑定新的函数。
因此最好这样实现
或者
更推荐后者,因为我们知道:
class Test{log=()=>{};}和 class Test{log(){}}
这两种写法的区别在于前者log是类的实例方法,而后者是原型方法,因此在构造函数中绑定,能让其他使用原型继承方法继承Test的组件可以继承到log方法。
如果使用函数式组件,应该使用useCallback这个hook。关于useCallback的使用,请参考本知识库的React进阶一文。
因为React在解析JSX时候需要将style对象解析成css style字符串。更推荐将样式写在CSS中。
如果在render方法进行setState,可能导致循环地进行diff工作。
让条件分支中只包含需要改动的元素,不包含不需要改动的元素,防止diff子节点和更新节点时候增加不必要的操作,消耗性能。
示例:
应该改成下面这种写法:
我们知道,Vue中有计算属性的能力,能够根据依赖的数据计算出我们关心的数据,而且有缓存的能力:依赖的值不变的话,不需要计算,直接返回结果。
React如果想要实现根据依赖的数据计算我们关心的数据,方法很简单。
但是这样实现没有缓存值的能力,当计算耗时较长时候会影响性能。
如何实现缓存值的能力呢?
可以使用memorize-one这个库:
如果使用函数式组件,可以使用useMemo来实现。关于useMemo库的使用,请参考本讲义中React进阶一文。
react-vitualize
启用concurrent mode之后,React会采取可中断渲染,让大规模的diff计算不会影响到界面的渲染,保证渲染和交互的流畅性。
使用Suspense组件可以在加载局部组件时候有更好的切换加载体验。
concurrent详细的介绍请阅读本系列concurrent mode文章。
不使用key或者用index作为key,都可能使列表在变化时候,让React无法辨别前后item对应关系,只能遍历对比,更新属性,这样可能会有多余的操作,造成性能损耗。
为什么需要key呢?我会单独写一篇文章详细讲解。
React官方提供了一个性能检测工具: react-addons-perf。
这个工具可以在渲染React应用时候打印各个组件的各种耗时,用来分析性能浪费。
其中比较重要的一个方法是printWasted(),可以打印并未更新组件的渲染操作,如果发现你的组件花了很长时间render和diff,但组件视图实际并未发生变化,那就要考虑是否需要引入PureComponent等优化渲染性能了。
基于React的虚拟滚动方案
在渲染列表时,一次性加载所有列表项可能导致页面响应缓慢,特别是在数据量大时。虚拟滚动是一种性能优化方法,它能显著改善用户体验和应用性能,特别适用于列表或表格形式的数据展示。本文将从固定高度和非固定高度两种场景深入探讨虚拟滚动的实现。
虚拟滚动的核心在于减少实际渲染的DOM元素数量,从而降低内存占用和渲染性能。通过计算容器高度、滚动位置以及元素高度,可以精确控制哪些元素需要渲染到DOM中,哪些则保持在内存中等待需要时再加载。固定高度的虚拟滚动在通用场景中较为适用,其原理是通过计算元素数量和高度得出容器高度,使用滚动位置与元素高度计算实际需要渲染的节点。
实现固定高度虚拟滚动时,我们首先计算容器高度和滚动容器的子元素。滚动容器的滚动条位置锁定在视口位置上,通过滚动距离和容器高度以及元素实际高度计算当前视口内实际需要渲染的节点。对于动态高度场景,即元素高度不可预知,我们采用渲染占位符的策略。通过预先渲染足够数量的占位符,可以将原始页面节点数量大幅减少,从而优化性能。
使用IntersectionObserver可以实现动态高度虚拟滚动。它观察目标元素与视口的交叉状态,通过渲染占位符,优化节点状态管理,根据视口位置更新节点状态,实现高效渲染。这样,当节点进入或离开视口时,仅渲染实际需要显示的部分,减少无效计算和渲染。
OnScroll监听方案则通过监听滚动事件,计算当前视口内的节点并渲染,实现动态高度的虚拟滚动。此方案相对简单,但可能导致性能问题,尤其是在大规模数据集处理时。优化方面,可以实现高度缓存机制,减少重复计算,提升性能。
综上,虚拟滚动通过减少DOM元素数量,优化渲染性能,提高用户体验。无论是固定高度还是动态高度的场景,采用合适的虚拟滚动策略都能有效提升应用性能,尤其是在大数据量展示时。通过实现不同虚拟滚动方案,可以针对具体需求灵活调整,以达到最佳效果。