说到这个我想起以前看到过的一个资料,也是讲这个问题的,他用了一个一万行的列表来做例子,原文在这里:high-performance-redux。下面这个例子来源于这篇文章:
function itemsReducer(state = initial_state, action) { switch (action.type) { case 'MARK': return state.map((item) => action.id === item.id ? {...item, marked: !item.marked } : item ); default: return state; } } class App extends Component { render() { const { items, markItem } = this.props; return ( <div> {items.map(item => <Item key={item.id} id={item.id} marked={item.marked} onClick={markItem} /> )} </div> ); } }; function mapStateToProps(state) { return state; } const markItem = (id) => ({type: 'MARK', id}); export default connect( mapStateToProps, {markItem} )(App);上面这段代码不复杂,就是一个App,接收一个items参数,然后将这个参数全部渲染成Item组件,然后你可以点击单个Item来改变他的选中状态,运行效果如下:
这段代码所有数据都在items里面,这个参数从顶层App传进去,当点击Item的时候改变items数据,从而更新整个列表。这个运行结果跟我们上面的Calendar有类似的问题,当单条Item状态改变的时候,其他没有涉及的Item也会更新。原因也是一样的:顶层的参数items改变了。
说实话,类似的写法我见过很多,即使不是从App传入,也会从其他大的组件节点传入,从而引起类似的问题。当数据量少的时候,这个问题不明显,很多时候都被忽略了,像上面这个图,即使一万条数据,因为每个Item都很简单,所以运行一万次render你也不会明显感知出来,在控制台看也就一百多毫秒。但是我们面临的Calendar就复杂多了,每个子节点的运算逻辑都更复杂,最终将我们的响应速度拖累到了七八秒上。
优化方案还是先说这个一万条的列表,原作者除了提出问题外,也提出了解决方案:顶层App只传id,Item渲染的数据自己连接redux store获取。下面这段代码同样来自这篇文章:
// index.js function items(state = initial_state, action) { switch (action.type) { case 'MARK': const item = state[action.id]; return { ...state, [action.id]: {...item, marked: !item.marked} }; default: return state; } } function ids(state = initial_ids, action) { return state; } function itemsReducer(state = {}, action) { return { // 注意这里,数据多了一个ids ids: ids(state.ids, action), items: items(state.items, action), } } const store = createStore(itemsReducer); export default class NaiveList extends Component { render() { return ( <Provider store={store}> <App /> </Provider> ); } } // app.js class App extends Component { static rerenderViz = true; render() { // App组件只使用ids来渲染列表,不关心具体的数据 const { ids } = this.props; return ( <div> { ids.map(id => { return <Item key={id} id={id} />; }) } </div> ); } }; function mapStateToProps(state) { return {ids: state.ids}; } export default connect(mapStateToProps)(App); // Item.js // Item组件自己去连接Redux获取数据 class Item extends Component { constructor() { super(); this.onClick = this.onClick.bind(this); } onClick() { this.props.markItem(this.props.id); } render() { const {id, marked} = this.props.item; const bgColor = marked ? '#ECF0F1' : '#fff'; return ( <div onClick={this.onClick} > {id} </div> ); } } function mapStateToProps(_, initialProps) { const { id } = initialProps; return (state) => { const { items } = state; return { item: items[id], }; } } const markItem = (id) => ({type: 'MARK', id}); export default connect(mapStateToProps, {markItem})(Item);这段代码的优化主要在这几个地方:
将数据从单纯的items拆分成了ids和items。
顶层组件App使用ids来渲染列表,ids里面只有id,所以只要不是增加和删除,仅仅单条数据的状态变化,ids并不需要变化,所以App不会更新。
Item组件自己去连接自己需要的数据,当自己关心的数据变化时才更新,其他组件的数据变化并不会触发更新。
拆解第三方库源码