A react-component for optimizing performance when it's parent re-renders.
MemoRender
是一个非常简单的 react 组件,它是为了某些追求性能场景下,阻止一些已经声明的组件在本身props
未发生变化(指深度比较 deep diff 后)时频繁重复渲染。
$ npm install memo-render --save
// yarn
$ yarn add memo-render
MemoRender
的使用非常简单,它默认情况下,你只需要将它嵌套包裹需要优化的组件节点即可。它会深度对比子节点对象的变化,以决定是否跳过react渲染。
- <HeavyComponent />
+ <MemoRender>
+ <HeavyComponent />
+ </MemoRender>
是否启用渲染优化
deps
是可选的。类似useMemo
useCallback
等 hooks 的第二个参数,即需要进行对比的依赖项数组。如果deps={[]}
,则表示任何情况下都不进行渲染更新。
使用deps
可以使对比更加高效。
<MemoRender deps={[this.state.name]}>
<div className={this.state.name}>...</div>
</MemoRender>
需要优化的组件节点
频繁的重复渲染是大多数事后导致应用下降的原因,而减少渲染这也正是 react 优化性能的最主要方向:Avoid Reconciliation
。大多数时候,我们应当尽可能的通过优化组件划分、组合逻辑、状态模型等,来避免非必要的组件被迫重复渲染。
但是受限于业务形态或者组件维护、业务逻辑限制等原因,有些组件无法从经常更新的上层组件中抽离。所以这时候就需要适用shouldComponentUpdate
等优化手段了。shouldComponentUpdate
仅适用于项目本身的组件,对于第三方组件无法适用该方法优化。
而MemoRender
就是用于这种优化场景,无需改造原有组件,直接将它放到需要优化的节点上层,即可达到一定的优化效果。这是因为组件传递的各种 props(包括 children),大多都是局部临时变量。对于 object 类型数据,局部临时生成,每次都是新的变量对象,这导致 react 内部的 diffing 比较不一致,持续进入下一层组件的ceconciliation
阶段,即重复渲染。MemoRender
正式通过深度比较children
节点是否有变化来告诉 react 是否跳过本次渲染。
感谢react-fast-compare
,MemoRender
借助 react 本身的react.memo
优化技巧,通过深度比较children
节点,来告诉 react 是否跳过本次渲染。
我们准备了一篇《高性能表单指南》,可以了解更多
首先,相信我,绝大多数情况下你都不需要MemoRender
。
MemoRender
并不是适用于所有场景,首先第一原则与 shouldComponentUpdate
/ React.memo
/ React.PureComponent
的指导思想一致:
If the slowdown is noticeable?
即,应当仅在应用性能出现明显下降时,再考虑应用这些优化手段。过度优化,可能导致应用存在潜在的 bug(例如组件无法响应 props 或 state 变化的更新)、优化逆反(过度的深度比较可能比 react 本身的 diffing、reconciliation 更慢)等
另外如果children
节点存在传递了局部内联函数(临时函数),MemoRender
会无法起到优化作用,甚至起到反作用,导致应用反而更慢。
/**
* Bad 错误示例
* 下面两个示例套用MemoRender是无效的,甚至会降低性能
* 因为 onChange 是一个始终变化的函数,而函数是无法深度比较的
* 第二个例子虽然传递的options是一个对象,但是因为其包含临时函数属性onChange,因此也会导致优化失效
*/
function APP() {
return (
<div>
<MemoRender>
<HeavyComponent onChange={() => {}} />
</MemoRender>
<MemoRender>
<HeavyComponent options={{ value: 'xx', onChange() {} }} />
</MemoRender>
</div>
);
}
正确的做法是使用deps
属性,或者创建不可变的 onChange
函数,例如放到组件实例(class 组件)或者适用 memoizeation 优化(function 组件、hooks):
/**
* Good 优化后
*/
function APP() {
const onChange = useCallback(() => {}, []);
return (
<div>
<MemoRender>
<HeavyComponent onChange={onChange} />
<HeavyComponent options={{ value: 'xx', onChange }} />
</MemoRender>
<MemoRender deps={[]}>
<HeavyComponent onChange={() => {}} />
</MemoRender>
</div>
);
}