Skip to content
A react-component for optimizing performance when it's parent re-renders.
JavaScript TypeScript
Branch: master
Clone or download

Latest commit

Fetching latest commit…
Cannot retrieve the latest commit at this time.

Files

Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
dist
npm
src
.editorconfig
.gitignore
README.md
package.json
rollup.config.js
tsconfig.json

README.md

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-compareMemoRender借助 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>
    );
}
You can’t perform that action at this time.