Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React性能优化之shouldComponentUpdate #4

Open
du1wu2lzlz opened this issue Jun 18, 2018 · 0 comments
Open

React性能优化之shouldComponentUpdate #4

du1wu2lzlz opened this issue Jun 18, 2018 · 0 comments

Comments

@du1wu2lzlz
Copy link
Owner

du1wu2lzlz commented Jun 18, 2018

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

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

React 的更新机制


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

Props & State

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

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

  • componentWillReceiveProps*
  • static getDerivedStateFromProps
  • shouldComponentUpdate
  • componentWillUpdate*
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate
  • 号标注的生命周期方法将会在 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


参考

@du1wu2lzlz du1wu2lzlz changed the title 用 Immutable 数据优化你的 React 应用 React性能优化之shouldComponentUpdate Jun 26, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant