Redux 学习笔记(三)

组件的重新渲染

说到 react 组件,肯定离不开组件的 props 和 state,我们可以在 props 和 state 存放任何类型的数据,通过改变 props 和 state,去控制整个组件的状态。当 props 和 state 发生变化时,react 会重新渲染整个组件

当组件的 props 或 state 变化,react 将会构建新的 virtual DOM,使用 diff 算法把新老的 virtual DOM 进行比较,如果新老 virtual DOM 树不相等则重新渲染,相等则不重新渲染。DOM 操作是非常耗时的,这导致重新渲染也非常的耗时,因此要提高组件的性能就应该尽一切可能的减少组件的重新渲染。

事实上根据 react 的更新规则,只要组件的 props 或 state 发生了变化就会重新渲染整个组件,这对性能是一个很大的浪费。如果对于复杂的页面,这将导致页面的整体体验效果非常差。因此要提高组件的性能,就应该想尽一切方法减少不必要的渲染。

组件优化

Pure Component

如果一个组件只和 props 和 state 有关系,给定相同的 props 和 state 就会渲染出相同的结果,那么这个组件就叫做纯组件,换一句话说纯组件只依赖于组件的 props 和 state,下面的代码表示的就是一个纯组件。

render() {
     return (
         <div style={{width: this.props.width}}>
                  {this.state.rows}
         </div>
     );
}

shouldComponentUpdate

shouldComponentUpdate 这个函数会在组件重新渲染之前调用,函数的返回值确定了组件是否需要重新渲染。函数默认的返回值是 true,意思就是只要组件的 props 或者 state 发生了变化,就会重新构建 virtual DOM,然后使用 diff 算法进行比较,再接着根据比较结果决定是否重新渲染整个组件。函数的返回值为 false 表示不需要重新渲染。 shouldComponentUpdate 在组件的重新渲染的过程中的位置如下图:

6

函数默认返回为 true,表示在任何情况下都进行重新渲染的操作,要减少重新渲染,只需要在一些不必要重新渲染的时候,使得函数的返回结果为 false。如果使得函数的结果一直为 false,这样不管组件的状态怎么变化,组件都不会重新渲染,当然一般情况下没有人会这样干。

PureRenderMixin

react 官方提供了 PureRenderMixin 插件,插件的功能就是在不必要的情况下让函数 shouldComponentUpdate 返回 false, 使用这个插件就能够减少不必要的重新渲染,得到一定程度上的性能提升,其使用方法如下:

import PureRenderMixin from 'react-addons-pure-render-mixin';
class FooComponent extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
  }

  render() {
    return <div className={this.props.className}>foo</div>;
  }
}

查看源码后发现这个插件其实就是重写了 shouldComponentUpdate 方法。

shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
}

重写的方法里面根据组件的目前的状态和组件接下来的状态进行浅比较,如果组件的状态发生变化则返回结果为 false,状态没有发生变化则返回结果为 true,把这个函数进行进一步的分解其实现如下:

shouldComponentUpdate(nextProps, nextState) {
    return !shallowEqual(this.props, nextProps) ||
            !shallowEqual(this.state, nextState);
}

就是分别去比较了函数的 props 和 state 的变化情况。

在 react 的最新版本里面,提供了 React.PureComponent 的基础类,而不需要使用这个插件。

状态比较

假设在每一个组件中都使用 PureRenderMixin 这个插件,我们来看一下使用这个插件后的状态的比较过程。假设我们有一个组件如下:

<Input size={100} color='red'>

我们想要去改变这个组件的颜色,使其变为 blue,

<Input size={100} color='blue'>

则状态的比较就是下面的对象的比较。

7

上图的比较是简单对象的比较,比较过程非常简单而且快速。但是如果是比较复杂的对象的比较,比如日期、函数或者一些复杂的嵌套许多层的对象,这些会比较耗时,甚至没法进行比较。

其实我们自己可以重写 shousldComponentUpdate 这个函数,使得其能够对任何事物进行比较,也就是深比较(通过一层一层的递归进行比较),深比较是很耗时的,一般不推荐这么干,因为要保证比较所花的时间少于重新渲染的整个组件所花的时间,同时为了减少比较所花的时间我们应该保证 props 和 state 尽量简单,不要把不必要的属性放入 state,能够由其他属性计算出来的属性也不要放入 state 中。

Immutable.js

对于复杂的数据的比较是非常耗时的,而且可能无法比较,通过使用 Immutable.js 能够很好地解决这个问题,Immutable.js 的基本原则是对于不变的对象返回相同的引用,而对于变化的对象,返回新的引用。因此对于状态的比较只需要使用如下代码即可:

shouldComponentUpdate() {
    return ref1 !== ref2;
}

这类比较是非常快速的。

动静分离
假设我们有一个下面这样的组件:

Children

对于嵌套多层、复杂的组件,组件的子节点很多,组件的更新的时间也将花费更多,并且难于维护,信息流从上往下由父组件传递到子组件单向流动,这可能会导致组件失去我们的控制。

children change over time
有如下的一个组件(现实中没人会这样写,这只是一个 demo),组件每1秒渲染一次。

class Parent extends Component {
    shouldComponentUpdate(nextProps) {
        return this.props.children != nextProps.children;
    }

    render() {
        return <div>{this.props.children}</div>;
    }

}

setInterval(() => {
    ReactDOM.render(
        <Parent>
            <div>child</div>
        </Parent>
    );
}, 1000);

通过在 shouldComponentUpdate 函数里面判断组件的 children 是否相等决定是否重新进行渲染,由于 children 是 props 的一个属性,应该每次都是一样的,组件应该不会重新渲染,可是事实上组件每次都会重新渲染。

让我们来看一下,children 的具体结构,如下图:

8

children 是一个比较复杂的对象,每次组件更新的时候都会重新构造,也就是说 children 是动态构建的,因此每次更新的时候都是不相等的。所以 shouldComponentUpdate每次都会返回 false,因此组件每次都会重新渲染。可以用一个变量来代替 childdren,这样每次构造的也会是相同的对象。

Independent children
再来看一个非常愚蠢的组件,如下:

class TwoColumnSplit extends Component {
    shouldComponentUpdate() {
        return false;
    }

    render() {
        return (
            <div>
                <FloatLeft>{this.props.children[0]}</FloatLeft>
                <FloatRight>{this.props.children[1]}</FloatRight>
            </div>
        );
    }
}

<TwoColumnSplit>
    <TargetContainer/>
    <BudgetContainer/>
</TwoColumnSplit>

通过在 shouldComponentUpdate 中返回 false,组件将不会因为外界的状态变化而发生改变,我们这样做是因为组件 TargetContainer 和 BudgetContainer 没有从它们的父元素获取任何信息,这样就不需要管外界的变化,把 children 和父组件进行了隔离,其实 TwoColumnSplit 就是起了隔离的作用。对于不需要从外界获取数据的组件,可以通过返回 false 来隔离外界的变化,减少重新渲染。

Container and Component

我们也可以通过组件的容器来隔离外界的变化。容器就是一个数据层,而组件就是专门负责渲染,不进行任何数据交互,只根据得到的数据渲染相应的组件,下面就是一个容器以及他的组件。

class BudgetContainer extends Component {
    constructor(props) {
        super(props);
        this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
    }

    computeState() {
        return BudgetStore.getStore()
    }

    render() {
        return <Budget {...this.state}/>
    }
}

容器不应该有 props 和 children,这样就能够把容器自己和父组件进行隔离,不会因为外界因素去重新渲染,也没有必要重新渲染。

设想一下,如果设计师觉得这个组件需要移动位置,你不需要做任何的更改只需要把组件放到对应的位置即可,我们可以把它移到任何地方,可以放在不同的应用中,同时也可以应用于测试,我们只需要关心容器的内部的数据的来源即可,在不同的环境中编写不同的容器。

9

总结

Purity => Use shouldComponentUpdate & PureRenderMixin

Data Comparability => Use highly comparable data (immutability)

Loose coupling => Use for maintainability and performance

Children => Be careful of children, children create deep update,
children are always change, we should insulate them from parent

参考资料

Making your app fast with high-performance components

React advanced-performance

React 应用的性能优化

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值