0.0.3 • Published 4 years ago

memo-render v0.0.3

Weekly downloads
16
License
-
Repository
github
Last release
4 years ago

MemoRender

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>

disabled

是否启用渲染优化

deps

deps是可选的。类似useMemo useCallback等 hooks 的第二个参数,即需要进行对比的依赖项数组。如果deps={[]},则表示任何情况下都不进行渲染更新。

使用deps可以使对比更加高效。

<MemoRender deps={[this.state.name]}>
    <div className={this.state.name}>...</div>
</MemoRender>

children

需要优化的组件节点

原理

频繁的重复渲染是大多数事后导致应用下降的原因,而减少渲染这也正是 react 优化性能的最主要方向:Avoid Reconciliation 。大多数时候,我们应当尽可能的通过优化组件划分、组合逻辑、状态模型等,来避免非必要的组件被迫重复渲染。

但是受限于业务形态或者组件维护、业务逻辑限制等原因,有些组件无法从经常更新的上层组件中抽离。所以这时候就需要适用shouldComponentUpdate等优化手段了。shouldComponentUpdate仅适用于项目本身的组件,对于第三方组件无法适用该方法优化。

MemoRender就是用于这种优化场景,无需改造原有组件,直接将它放到需要优化的节点上层,即可达到一定的优化效果。这是因为组件传递的各种 props(包括 children),大多都是局部临时变量。对于 object 类型数据,局部临时生成,每次都是新的变量对象,这导致 react 内部的 diffing 比较不一致,持续进入下一层组件的ceconciliation阶段,即重复渲染。MemoRender正式通过深度比较children节点是否有变化来告诉 react 是否跳过本次渲染。

感谢react-fast-compareReactRender借助 react 本身的react.memo优化技巧,通过深度比较children节点,来告诉 react 是否跳过本次渲染。

请注意,这并非意味着children节点的组件不会运行,实际上children节点的组件依然会调用其render()方法,但是如果render()生成的react elements tree(Virtual DOM Tree)与上一次没有变化,则 react 会复用上一次的结果,不进入协调(Reconciliation)阶段。

对性能影响更大的是协调(Reconciliation)阶段的计算处理,包括更新真实 DOM。仅仅创建VDOM的开销相比是可以忽略不计的。

陷阱

首先,相信我,绝大多数情况下你都不需要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>
    );
}