• 保证渲染的性能
    • 第一个例子
      • 坏实践
      • 好实践
    • 第二个例子
      • 坏实践
      • 又一个坏实践
      • 好实践
      • 坏实践
      • 好实践
  • 参考资料:

    保证渲染的性能

    为了保障组件的性能, 我们有的时候会从组件渲染的角度出发.

    更干净的render函数? 这个概念可能会有点让人疑惑.

    其实在这里干净是指我们在shouldComponentUpdate这个生命周期函数里面去做浅比较, 从而避免不必要的渲染.

    关于上面的干净渲染, 现有的一些实现包括React.PureComponent, PureRenderMixin, recompose/pure 等等.

    第一个例子

    坏实践
    1. class Table extends PureComponent {
    2. render() {
    3. return (
    4. <div>
    5. {this.props.items.map(i =>
    6. <Cell data={i} options={this.props.options || []}/>
    7. )}
    8. </div>
    9. );
    10. }
    11. }

    这种写法的问题在于{this.props.options || []}- 这种写法会导致所有的Cell都被重新渲染即使只有一个cell发生了改变. 为什么会发生这种事呢?

    仔细观察你会发现, options这个数组被传到了Cell这个组件上, 一般情况下, 这不会导致什么问题. 因为如果有其他的Cell组件, 组件会在有props发生改变的时候浅对比props并且跳过渲染(因为对于其他Cell组件, props并没有发生改变). 但是在这个例子里面, 当options为null时, 一个默认的空数组就会被当成Props传到组件里面去. 事实上每次传入的[]都相当于创建了新的Array实例. 在JavaScript里面, 不同的实例是有不同的实体的, 所以浅比较在这种情况下总是会返回false, 然后组件就会被重新渲染. 因为两个实体不是同一个实体. 这就完全破坏了React对于我们组件渲染的优化.

    好实践
    1. const defaultval = []; // <--- 也可以使用defaultProps
    2. class Table extends PureComponent {
    3. render() {
    4. return (
    5. <div>
    6. {this.props.items.map(i =>
    7. <Cell data={i} options={this.props.options || defaultval}/>
    8. )}
    9. </div>
    10. );
    11. }
    12. }

    第二个例子

    在render函数里面调用函数也可能导致和上面相同的问题.

    坏实践
    1. class App extends PureComponent {
    2. render() {
    3. return <MyInput
    4. onChange={e => this.props.update(e.target.value)}/>;
    5. }
    6. }
    又一个坏实践
    1. class App extends PureComponent {
    2. update(e) {
    3. this.props.update(e.target.value);
    4. }
    5. render() {
    6. return <MyInput onChange={this.update.bind(this)}/>;
    7. }
    8. }

    在上面的两个坏实践中, 每次我们都会去创建一个新的函数实体. 和第一个例子类似, 新的函数实体会让我们的浅比较返回false, 导致组件被重新渲染.
    所以我们需要在更早的时候去bind我们的函数.

    好实践
    1. class App extends PureComponent {
    2. constructor(props) {
    3. super(props);
    4. this.update = this.update.bind(this);
    5. }
    6. update(e) {
    7. this.props.update(e.target.value);
    8. }
    9. render() {
    10. return <MyInput onChange={this.update}/>;
    11. }
    12. }
    坏实践
    1. class Component extends React.Component {
    2. state = {clicked: false};
    3. onClick() {
    4. this.setState({clicked: true})
    5. }
    6. render() {
    7. // 如果options为空的话, 每次都会创建一个新的{test:1}对象
    8. const options = this.props.options || {test: 1};
    9. return <Something
    10. options={options}
    11. // New function created each render
    12. onClick={this.onClick.bind(this)}
    13. // New function & closure created each render
    14. onTouchTap={(event) => this.onClick(event)
    15. />
    16. }
    17. }
    好实践
    1. class Component extends React.Component {
    2. state = {clicked: false};
    3. options = {test: 1};
    4. onClick = () => {
    5. this.setState({clicked: true})
    6. };
    7. render() {
    8. // Options这个对象只被创建了一次.
    9. const options = this.props.options || this.options;
    10. return <Something
    11. options={options}
    12. onClick={this.onClick} // 函数只创建一次, 只绑定一次
    13. onTouchTap={this.onClick} // 函数只创建一次, 只绑定一次
    14. />
    15. }
    16. }

    参考资料:

    • @esamatti/react-js-pure-render-performance-anti-pattern-fb88c101332f"">https://medium.com/@esamatti/react-js-pure-render-performance-anti-pattern-fb88c101332f
    • https://github.com/nfour/js-structures/blob/master/guides/react-anti-patterns.md#pure-render-immutability
    • Optimizing React Rendering