Redux + Immutable.js 性能优化

(阅读本文约需 2 分钟)javascript

引言

众所周知,在使用 Redux 时最麻烦的一个部分就是 reducer 的编写,因为 Redux 要求状态是 immutable 的,也就是说,发生变化的状态树必定是一个新的引用。 因此 reducer 常常会写成这样:java

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    default:
      return state
  }
}
复制代码

不少人会称之为深克隆,其实并非,这个过程既不是深克隆也不是浅克隆。git

reducer 的正确写法

首先咱们来谈谈深克隆是否可行,若是你的 reducer 在每次状态发生变化时都进行深克隆处理,你的 app 毋庸置疑是能够 work 的,Time Travelling 固然也能够用,那么问题会出在哪里呢?github

咱们不妨经过图示来看一下:redux

整个状态树被重建了,这就意味着 PureComponentshouldComponentUpdate 没有实现好的组件都会从新 render。markdown

因此在实际项目中,咱们引入了 Immutable.js,就是为了不写出繁琐或者不正确的 reducer。相似的还有 immer 这样的库。app

Immutable.js 内部会使用 Shared Structure 来避免深克隆,一方面提高了 Immutable.js 自身的性能,另外一方面能帮助 React 更高效地渲染。就像这样:函数

当一个对象中的一个键发生变化时,这个对象中其余键的值不会有任何变化,而引用该对象的对象会产生一份新的引用,以此类推。这样,咱们的状态树就能够像值类型同样进行对比了:oop

节点 4 发生变化,节点 一、2 变化先后必定不相等,可是节点 三、五、6 没有变化仍然是相等的。咱们甚至不用 deepEquals,对比引用就能够了,由于 Immutable.js 能够保证它们不发生变化。性能

所以,咱们的 React 组件若是采用了 PureComponent,就能自动得到最好的优化,与变化无关的组件也不会从新渲染。

Immutable.js 与 React 配合的正确用法

然而在实际使用中,咱们又遇到了问题,即使使用了 Immutable.js,每次更新时仍是有不少无关组件发生更新了。搜查了一遍代码,我发现咱们如今有不少这样的写法:

const mapStateToProps = state => {
  const user = selectCurrentUser(state)
  const me = user.toJS()
  const myTeam = selectMyTeam(state)
  const team = myTeam && myTeam.toJS()
  //...
  return { user, me, myTeam, team /*, ...*/ }
 }
复制代码

问题就出在 toJS 的调用上,根据文档:

Deeply converts this Keyed collection to equivalent native JavaScript Object.

toJS 会将本来 structure shared 的对象彻底深克隆一遍,全部 PureComponent 又会从新渲染。能够看一下咱们如今的状况:

能够看到,改变了一个与左侧边栏无关的按钮状态的时候,左侧边栏依旧从新渲染了。

下面是去掉了 toJS 调用后的状况:

是否是好多了。

总结

至此咱们也可以得出结论了,React 的渲染性能很大一部分取决于更新的粒度,当咱们的 render 函数已经足够庞大时,咱们可以作的只有分步更新(Fiber 和 Time Slicing 主要解决的问题)和精准更新了。

而要作到精准更新,就必定要处理好状态的变化,其实最简单的方法就是状态扁平化,对象层级越小,咱们的代码里可能出现的问题就越少。另外,尽量将 connect 放置在须要状态的组件外,目前咱们仍是有不少组件过早 connect,而后将状态一层一层经过 props 传下去,这也是状态对象层级太深(有多深我就不截图了...)致使的。Redux 状态更新(dispatch)时,全部的 Connect(...) 组件都会根据本身的 mapped state 进行更新,越早 connect 的组件越有可能发生更新,而其子组件若是没有处理好 shouldComponentUpdate 就会出现许多无用的更新,白白损失性能。


References:

  1. Immutable Data Structures and JavaScript
  2. Reducers - Redux
  3. Map -- Immutable.js
相关文章
相关标签/搜索