原标题:揭秘Vue高效运行技巧:性能提升秘籍,React性能调优全攻略
导读:
Intro...
React性能提升策略 在采用React构建的项目中,我们应从加载效率和运行效率两个维度着手进行优化。加载效率优化的宗旨是加快用户界面的展现速度,提升与应用的交互响应速度。而运行效率优化的目标则是减少卡顿现象,使交互过程更加流畅。
众所周知,React的setState方**引发diff和更新过程。默认情况下,React会对整个组件树进行对比,但在许多情况下,这种diff操作是不必要的,因为某些子组件的props并未发生变化,从而无需进行diff操作。
为了避免对未发生变化的子组件进行不必要的diff操作,React提供了shouldComponentUpdate生命周期钩子,其函数签名应为shouldComponentUpdate(nextProps, nextState)。当此钩子返回true时,将执行后续的render和diff操作;若返回false,则React将停止向下执行。用户可以在该生命周期钩子中进行state和props的对比,以判断是否需要进行更新。一般情况下,若当前组件的props与nextProps属性值相同,且state的属性值也相同,则无需进行更新。
React.PureComponent实现了shouldComponentUpdate方法,并采用了浅比较机制。对于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。 此外,Fragment也可以简写。
在注册事件回调时,不建议使用匿名函数或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样式字符串,因此更推荐将样式写在CSS中。
如果在render方法中进行setState,可能会导致循环地进行diff操作。
让条件分支中只包含需要改动的元素,不包含不需要改动的元素,以防止在diff子节点和更新节点时增加不必要的操作,从而消耗性能。
示例: 应该改成下面这种写法: 我们知道,Vue中有计算属性的能力,能够根据依赖的数据计算出我们关心的数据,并且具有缓存的能力:依赖的值不变时,无需重新计算,直接返回结果。
React如果想要根据依赖的数据计算我们关心的数据,方法很简单。但是,这样实现没有缓存值的能力,当计算耗时较长时会影响性能。
如何实现缓存值的能力呢? 可以使用memorize-one这个库: 如果使用函数式组件,可以使用useMemo来实现。关于useMemo库的使用,请参考本讲义中React进阶一文。
react-virtualize 启用concurrent mode之后,React会采取可中断渲染,确保大规模的diff计算不会影响到界面的渲染,保证渲染和交互的流畅性。
使用Suspense组件可以在加载局部组件时提供更好的切换加载体验。
concurrent的详细介绍请阅读本系列concurrent mode文章。
不使用key或使用index作为key,都可能导致React在列表变化时无法辨别前后item的对应关系,只能遍历对比、更新属性,从而可能产生多余的操作,造成性能损耗。
为什么需要key呢?我会单独写一篇文章详细讲解。
React官方提供了一个性能检测工具:react-addons-perf。 该工具可以在渲染React应用时打印各个组件的各种耗时,用于分析性能瓶颈。
其中比较重要的一个方法是printWasted(),可以打印未更新的组件的渲染操作。如果发现某个组件花费了很长时间进行render和diff,但组件视图实际并未发生变化,那么就需要考虑是否需要引入PureComponent等优化渲染性能。
记录React性能优化之“虚拟滚动”技术——react-window 如果你的应用需要渲染长列表(上百甚至上千的数据)时,React官网推荐我们使用“虚拟滚动”技术。这项技术会在有限的时间内仅渲染有限的内容,并奇迹般地降低重新渲染组件消耗的时间,以及创建DOM节点的数量。
在您的应用处理大量数据渲染(数量达数百甚至数千)时,React官方建议采用“虚拟滚动”策略。该策略能在短时间内仅渲染部分内容,神奇地减少组件重渲染所需时间和DOM节点创建量。
React官方推崇使用react-window与react-virtualized这两大流行的虚拟滚动库。它们提供了丰富的可重用组件,适用于展示列表、网格和表格数据。 这两个库均由同一作者创作。react-virtualized最初是作者在不太了解React与窗口概念时编写的,添加了API以及过多的非必要功能和组件,后来作者对此感到懊悔。但由于一旦向开源项目添加内容,删除它对用户来说会非常不便。因此,作者对react-virtualized进行了全面重构,并更加注重减小体积和提升速度。因此,react-window可以看作是react-virtualized的轻量级替代品。 我采用的是VariableSizeGrid(可变尺寸网格)。 问题1:在通过itemData传递网格数据时,若columnCount(网格列数)乘以rowCount(网格行数)大于itemData.length,则在网格滚动至最后一行时,最后一行内容未渲染。 解决方案1:向itemData数组追加(columnCount * rowCount - itemData.length)个对象{true:true},并在组件渲染时进行判断,返回
。 问题2:网格能否100%填充页面的宽度或高度?(作者曾在npm上回答过此问题) 解决方案2:网格的宽高必须传入number类型,因此不能直接使用'100%',需要借助react-virtualized-auto-sizer包。 问题3:这一点较为关键,没有提供可以传递方法的API。虽然提供了可以在外层附加自定义属性或事件处理程序的API:outerElementType,但这并不能满足我在点击按钮时触发事件的需求。 解决方案3:采用JavaScript设计模式中的观察者模式。