(阅读本文约需 2 分钟)
引言
众所周知,在使用 Redux 时最麻烦的一个部分就是 reducer 的编写,由于 Redux 要求状态是 immutable 的,也就是说,发生变化的状态树一定是一个新的引用。 所以 reducer 经常会写成这样:
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}
}
很多人会称之为深克隆,其实并不是,这个过程既不是深克隆也不是浅克隆。
reducer 的正确写法
首先我们来谈谈深克隆是否可行,如果你的 reducer 在每次状态发生变化时都进行深克隆处理,你的 app 毋庸置疑是可以 work 的,Time Travelling 当然也可以用,那么问题会出在哪里呢?
我们不妨通过图示来看一下:
整个状态树被重建了,这就意味着 PureComponent
和 shouldComponentUpdate
没有实现好的组件都会重新 render。
所以在实际项目中,我们引入了 Immutable.js,就是为了避免写出繁琐或者不正确的 reducer。类似的还有 immer 这样的库。
Immutable.js 内部会使用 Shared Structure 来避免深克隆,一方面提升了 Immutable.js 自身的性能,另一方面能帮助 React 更高效地渲染。就像这样:
当一个对象中的一个键发生变化时,这个对象中其他键的值不会有任何变化,而引用该对象的对象会产生一份新的引用,以此类推。这样,我们的状态树就可以像值类型一样进行对比了:
节点 4 发生变化,节点 1、2 变化前后一定不相等,但是节点 3、5、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
就会出现许多无用的更新,白白损失性能。
Immutable toJS
- 问题
当我用 immutable 里面的 toJS 方法去使一个 immutable 对象数组变为正常的数组时,发生了报错
报错信息:TypeError: list.toJS is not a function
让人疑惑的是,我第一次去 yarn start 的时候他是不会报错的,后面就有了,重开服务器又好了,然后做出修改热更新之后又报错QAQ
- 列表项目
index 代码
const newList = list.toJS();
reducer 代码
const defaultState = fromJS({list: [1,2,3,4,5]
});