du1wu2lzlz / my_blog

my personal blog
1 stars 0 forks source link

React性能优化之shouldComponentUpdate #4

Open du1wu2lzlz opened 6 years ago

du1wu2lzlz commented 6 years ago

一直以来,Virtual DOM 都是 React 的一大特色,Facebook 宣称 React 借其能很大程度提高 SPA 的性能表现。但这就意味着 React 的性能一定优秀吗,可能并不是,在某些情况下,React 慢的令人抓狂,这时你可能就需要用一些正确的手段来优化它了。

而shouldComponentUpdate() 一直是React性能优化的核心问题,来避免不必要的渲染

React 的更新机制


我们不妨先简单了解下 React 的更新机制,如果能降低它的更新频率,自然能大大提高整体渲染速度。

Props & State

props 和 state 的基本概念不再赘述,组件的 props 是只读的,只能通过上层 JSX 声明时的属性传递进来,state 则完全受组件自身控制,并且只存在于class语法声明的组件。

无论是 props 还是 state 发生变化都可以触发组件更新,下面这些生命周期方法会在组件重新渲染时被依次调用:

  • 号标注的生命周期方法将会在 React 17 移除,一旦调用了新的生命周期方法,这些方法将不会被调用

01

从上面的生命周期中我们可以看到,shouldComponentUpdate 方法将在组件接收到新的 props 或者 state 时被调用。然而在默认情况下, 每次更新,React 都会去调用 render 方法重新生成 Virtual DOM 并通过 diff 算法计算出需要变动的部分,然后操作 DOM 完成这部分更新。

对于一些简单的 React 应用来说,每次 render 带来的消耗不会特别大,不过一旦你的应用有了一定规模,尤其是复杂的树形结构时,每次更新都会消耗不少的系统资源。

shouldComponentUpdate(SCU)

我们先来看下官方文档里的示意图。

image

从图中可以看到,在这个简单的树形结构中,仅仅是 c7 的状态发生了改变,所有的组件都要进行一次 render,那如果我这个树下有 10 个组件呢,50 个呢?尤其当这个 c7 的状态变化与鼠标移动这种高频操作相关时,所有的组件不停的重新生成 Virtual DOM,这样能有多卡顿你能想象的到吗?

如果不用 SCU 对 React 的更新进行限制,你可能像我之前一样,对着 Chrome 的 Perfomance 工具里锯齿般的火焰图束手无策。那假如 SCU 可以正确的感知数据变化并返回你期待的结果,实际情况又会如何呢?

如上所示,如果 SCU 正常工作,只会发生 3 次 Virtual DOM 的比较,换言之,只有发生改变的 c7 以及它的父级组件会进入 render 方法,生成 Virtual DOM。那这次如果我们有 100 个子组件,但 c7 的深度还是 3 呢?没错,它依然是只会调用 3 次 render 方法,在大型树形结构里,这样的渲染效率无疑是成几何倍提升。

那么问题又来了,SCU 是一定要实现的,但在每个组件中都手写 SCU,手动地比较复杂的对象中每个键的值,难度非同一般,那么如何轻松地让 SCU 返回你期待的结果?

解决思路

虽然完全手写 SCU 不现实,但这里依然有一些组合方案可以助我们实现目标

PureComponent

PureComponent 是 React 提供的另一个组件,它默认帮你实现了 SCU 方法,其实在它出现之前,它的前身是 React 的 addons 提供的 PureRenderMixin,它的源码如下:

var shallowEqual = require('fbjs/lib/shallowEqual');

module.exports = {
  shouldComponentUpdate: function(nextProps, nextState) {
    return (
      !shallowEqual(this.props, nextProps) ||
      !shallowEqual(this.state, nextState)
    );
  }
};

我们可以看到它帮我们实现了 SCU 方法,实现的机制是浅比较(Shallow Compare),也就是说,它只简单的比较了 this.propsnextProps 两个变量(以及他们的第一层子属性)引用的是否为同一个地址,如果是则返回 false,否则返回 true

虽然 PureComponent 帮我们实现了 SCU 方法,但这并不意味着我们已经达到目标了,别忘了它只是实现了浅比较,在 JavaScript 中,Primitive 数据能直接的用 = 号简单的浅比较,而 Object 数据仅仅表示两个变量引用的堆地址相同,但这块儿内存中的数据有没有改动过,就无从得知了,看个简单的例子:

oldState = { expand: true };
oldState.expand = false;
newState = oldState;

shallowEqual(newState, oldState) // true   浅比较

如上我们更新了 state 的 expand 的值,但 PureComponent 在比较时会认为 state 并没有更新返回 SCU 返回 false,这样我们的组件就得不到正确的更新了。

深拷贝就行了吗

优雅的 Immutable 数据

Immutable 即不可变的,意思是对象创建后,无法通过简单的赋值更改值或引用。Facebook 推出了 ImmutableJS 来实现这套机制,它有自己的一套 API 来对已有的 Immutable 对象进行修改并返回一个全新的对象,但与深拷贝不同,这个对象只修改了变动的部分,示意如下:

// 示意图怎么传不上去呀

ImmutableJS

Facebook 推荐使用 ImmutableJS 来优化 React 应用,但使用它的同时也意味需要重新学习大量的 API


参考