性能评估工具
性能时间线
当在开发环境时,只需在URL末尾加上?react_perf。然后查看Google Chrome开发者工具里的Performance标签页
Redux优化
连接正确的组件
前面描述过组件的简单策略:默认情况下从展示型组件开始,一旦它们变得过于臃肿,不访问Redux store就无法维护,就用connect将它们包装起来。
现在,我们将提供工具指导性的原则:如果能避免开销很大的重绘,就连接组件。
自上而下的方法
redux带来的一个好处时,数据所有组件都是潜在可用的,这对数据流动提供了很大的灵活性。
简单策略,虽然简单实用。但有性能成本。由于只有一个入口点,因此只要redux中的数据发生改变,整个APP就会试图重新渲染。因为它们都是App组件的子组件,所以每次App重绘时(在某个action派发后),Header 和 Taskspage也都会尝试重新渲染。
将其他组件连接到redux
若能正确使用connect,就不必编写自己的shouldComponentUpdate逻辑。将Header和TasksPage直接连接到redux,会怎样?每个组件都将使用选择器来获取它们自己所需的数据 - 不多也不少。
这是一种非常强大的理念:可使connect来提高性能而不是用自定义的shouldComponentupdates,而且这也非常适合组织及解耦规模不断增长的APP。
合理吗?这些改动是由性能推动的,但从职责分离的角度看,这样也更有意义。当APP随着时间推移而规模增长时,以即将进行的方式连接其他组件,可防止任何模块变得臃肿与混乱。
mapStateToProps和记忆型选择器
完成上述代码变更后,Header仍会重绘。答案在于更基础的部分 - JS中的引用检查和connect的浅层对比。 src/reducers/index.js
export const getProjects = state => {
return Object.keys(state.projects.items).map(id => {
return projects.items[id];
})
}
当考虑connect如何检查任意属性是否已经改变时,问题来了。我们使用connect存储mapStateToProps的返回值,并且当渲染操作被触发时与新的属性进行比较。这是一种浅层相等性比较,而不会深入任何内嵌对象。
这与Header有何关系?为什么组件仍进行不必要的重绘?redux中的每个状态在发生更改后都会运行getProjects,但返回值没有记忆。每次调用getProjects时,无论数据是否已更改,都将返回一个新的对象。因此connect的浅层相等性检查永远不会通过,而react-redux会认为Header有新的数据需要渲染。
解决方案是使用reselect将getProjects更改为记忆现选择器
export const gfetProjects = createSelector(
[state => state.projects],
projects => {
return Object.keys(state.projects.items).map(id => {
return projects.items[id];
})
}
)
最有效的性能优化是“做更少的东西”,这就是此处达到的效果。
connect高级用法的法则
- 通过connect可避免使用shouldComponentUpdate.
- 以更精细的方式使用connect,这对规范化数据最有效
- 以这样的方式使用connect是一种架构选择,而在某些情况下可能产生 一些问题。