上面通过使用调试工具我看到了一个熟悉的现象,并猜到了他慢的原因,但是目前仅仅是猜测,具体是不是这个原因还要看看他的源码才能确认。好在我在看他的源码前先去看了下,然后发现了这个:
react-big-calendar接收两个参数onSelectEvent和selected,selected表示当前被选中的事件(预定),onSelectEvent可以用来改变selected的值。也就是说当我们选中某个预定的时候,会改变selected的值,由于这个参数是从顶层往下传的,所以他会引起下面所有子节点的更新,在我们这里就是差不多7000个背景格子 + 1399个其他事件,这样就导致不需要更新的组件更新了。
顶层selected换成Context?react-big-calendar在顶层设计selected这样一个参数是可以理解的,因为使用者可以通过修改这个值来控制选中的事件。这样选中一个事件就有了两个途径:
用户通过点击某个事件来改变selected的值
开发者可以在外部直接修改selected的值来选中某个事件
有了前面一万条数据列表优化的经验,我们知道对于这种问题的处理办法了:使用selected的组件自己去连接Redux获取值,而不是从顶部传入。可惜,react-big-calendar并没有使用Redux,也没有使用其他任何状态管理库。如果他使用Redux,我们还可以考虑添加一个action来给外部修改selected,可惜他没有。没有Redux就玩不转了吗?当然不是!React其实自带一个全局状态共享的功能,那就是Context。React Context API官方有详细介绍,我之前的一篇文章也介绍过他的基本使用方法,这里不再讲述他的基本用法,我这里想提的是他的另一个特性:使用Context Provider包裹时,如果你传入的value变了,会运行下面所有节点的render函数,这跟前面提到的普通props是一样的。但是,如果Provider下面的儿子节点是PureComponent,可以不运行儿子节点的render函数,而直接运行使用这个value的孙子节点。
什么意思呢,下面我将我们面临的问题简化来说明下。假设我们只有三层,第一层是顶层容器Calendar,第二层是背景的空白格子(儿子),第三层是真正需要使用selected的事件(孙子):
示例代码如下:
// SelectContext.js // 一个简单的Context import React from 'react' const SelectContext = React.createContext() export default SelectContext; // Calendar.js // 使用Context Provider包裹,接收参数selected,渲染背景Background import SelectContext from './SelectContext'; class Calendar extends Component { constructor(...args) { super(...args) this.state = { selected: null }; this.setSelected = this.setSelected.bind(this); } setSelected(selected) { this.setState({ selected }) } componentDidMount() { const { selected } = this.props; this.setSelected(selected); } render() { const { selected } = this.state; const value = { selected, setSelected: this.setSelected } return ( <SelectContext.Provider value={value}> <Background /> </SelectContext.Provider> ) } } // Background.js // 继承自PureComponent,渲染背景格子和事件Event class Background extends PureComponent { render() { const { events } = this.props; return ( <div> <div>这里面是7000个背景格子</div> 下面是渲染1400个事件 {events.map(event => <Event event={event}/>)} </div> ) } } // Event.js // 从Context中取selected来决定自己的渲染样式 import SelectContext from './SelectContext'; class Event extends Component { render() { const { selected, setSelected } = this.context; const { event } = this.props; return ( <div className={ selected === event ? 'class1' : 'class2'} onClick={() => setSelected(event)}> </div> ) } } Event.contextType = SelectContext; // 连接Context 什么是PureComponent?我们知道如果我们想阻止一个组件的render函数运行,我们可以在shouldComponentUpdate返回false,当新的props相对于老的props来说没有变化时,其实就不需要运行render,shouldComponentUpdate就可以这样写:
shouldComponentUpdate(nextProps) { const fields = Object.keys(this.props) const fieldsLength = fields.length let flag = false for (let i = 0; i < fieldsLength; i = i + 1) { const field = fields[i] if ( this.props[field] !== nextProps[field] ) { flag = true break } } return flag }这段代码就是将新的nextProps与老的props一一进行对比,如果一样就返回false,不需要运行render。而PureComponent其实就是React官方帮我们实现了这样一个shouldComponentUpdate。所以我们上面的Background组件继承自PureComponent,就自带了这么一个优化。如果Background本身的参数没有变化,他就不会更新,而Event因为自己连接了SelectContext,所以当SelectContext的值变化的时候,Event会更新。这就实现了我前面说的如果Provider下面的儿子节点是PureComponent,可以不运行儿子节点的render函数,而直接运行使用这个value的孙子节点。
PureComponent不起作用