React component re-render

2,580 阅读3分钟

由列表优化谈起re-render

why re-render?

这两天在看项目的渲染优化题,决定总结一下,发现不论是代码写法和组件规划以及列表之间的交互,都是有可能会引起性能问题的罪魁祸首。

首先分析一下,组件何时更新?为何更新?

how does React decide to re-render a component

  1. component state change

    举个栗子:

    class Test extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                value: 0
            }
        }
        handleClick = () => {
            this.setState({
                value: this.state.value ++
            })
        }
        render() {
            return (<div onClick={this.handleClick}>{this.state.text}</div>)
        }
    }

    但是大家都知道,react的实现思想就是virtual DOM,每次state的改变都会引起re-render,不管state里的值是否真正用到了组件中,我从一遍博客中看到这样的描述:A re-render can only be triggered if a component’s state has changed. The state can change from a props change, or from a direct setState change. The component gets the updated state and React decides if it should re-render the component. Unfortunately, by default React is incredibly simplistic and basically re-renders everything all the time.那么什么东西适合放在state里----需要持续维护的一个局部状态或者跟UI相关的状态才去放在state里,而那些只是暂时性的东西或者特定函数里才会用到的,只需要放在组件实例中。最后一句话:state虽灵活,用时需谨慎。

  2. component props change

    先放栗子:

    class Mine extends React.Component {
        render() {
            const { childIds } = this.props
            return (
              <FileLis childIds={childIds} />
            )
        }
    }
    ​
    export default connect((state, ownProps) => {
        childIds: selectFileChildIds(state, ownProps.match.params.guid || '0')
    })(Mine)

    很简单,当数据改变的时候必然会re-render

  3. react component re-render in these instances

    • Parent component re-render

    • Calling this.setState() within the component 并且会依次根据react 生命周期更新 shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

    • component's props changes. 并且依次走生命周期 componentWillReceivePorps -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate(connect method of react-redux trigger this when there are applicable changes in the Redux store)

    • calling this.forceUpdate which is similar to this.setState

Re-render necessary or unnecessarily?

什么是必须的re-render什么是不必要的re-render,我们可以借助react-devtools,勾选Highlight Updates,


然后click button or do some thing 引起组件的render,这里有个todo list highlight-demo.firebaseapp.com/你会看到页面上会出现蓝色,绿色, 黄色,红色。其中蓝色是指最不频繁的更新,其次是绿色,黄色,红色。黄色和红色并不一定是不好的,之所以出现黄色或者红色,组件这个时候确实因为某些state或者props改变导致了频繁更新。

比如项目中桌面列表的渲染:


只是简单的修改了某个文件的一个属性,会看到整个list都重绘了一遍,设置右边和左边的侧栏以及header都re-render了,这就是不必要的re-render

优化列表以后的效果:


可以看到绿色部分只是修改的file发生了re-render

why did you updated

在优化列表渲染的时候,用到了一个辅助工具--why-did-you-update

既然列表在重复渲染,那就要知道到底哪个组件哪个东西引起了重复渲染,其实我们是有线索可以查找的,然后借助why-did-you-update这个工具会帮助我们在控制台显示哪些组件在重复渲染,以及什么原因导致的。

Why-did-you-update 是个npm 包,npm install --save why-did-you-update or yarn add --save why-did-update

开发环境下使用它

import Rect from 'react'
if (process.env.NODE_ENV !== 'production') {
    const { whyDidYouUpdate } = require('why-did-you-update')
    whyDIdYouUpdate(React)
}

分析结果

减少或者控制不必要的re-render

use PureComponent

合理使用props传递数据

计算放在connect里

尽量不要传递state

不要在react component render 里使用箭头函数

class Item extends React.Component {
    render() {
        const { mode } = this.props
        return (
            <div onCick={() => this.handleClick(mode)}>...</div>
        )
    }
}

👆的做法每次render都会生成一个新的匿名函数,然而其实函数并没有变只是引用变了,造成re-render。The problem with using an arrow function in the render call is it will create a new function every time, which ends up causing unneeded re-renders

参考资料

lucybain.com/blog/2017/r…

stackoverflow.com/questions/4…

github.com/maicki/why-…