6) React Native 组件生命周期(ES6)

在React Native中使用组件来封装界面模块时,整个界面就是一个大的组件,开发过程就是不断优化和拆分界面组件、构造整个组件树的过程。
所以学习理解组件的生命周期显得尤为重要!

一、组件的属性(props)和状态(state)

1. 属性(props)

它是组件的不可变属性(组件自己不可以自己修改props)。
组件自身定义了一组props作为对外提供的接口,展示一个组件时只需要指定props作为节点的属性。
一般组件很少需要对外公开方法(例外:工具类的静态方法等),唯一的交互途径就是props。所以说它也是父组件与子组件通信的桥梁
组件自己不可以自己修改props(即:props可认为是只读的),只可由其他组件调用它时在外部修改。

2. 状态(state)

它是组件的内部状态属性,主要用来存储组件自身需要的数据。
除了初始化时可能由props来决定,之后就完全由组件自身去维护。
组件中由系统定义了setState方法,每次调用setState时都会更新组件的状态,触发render方法重新渲染界面
需要注意的是render方法是被异步调用的,这可以保证同步的多个setState方法只会触发一次render,这样做是有利于提高性能的。

二、组件的生命周期

对于自定义组件,除了必须实现的render方法,还有一些其他的可选方法可被调用。这些方法会在组件的不同时期之行,所以也可以说这些方法是组件的生命周期方法。
对于组件的生命周期来说一般分为四个阶段,分别为:
**创建阶段、实例化阶段、运行(更新)阶段、销毁阶段。 **

1. 创建阶段

该阶段主要发生在创建组件类的时候,在这个阶段中会初始化组件的属性类型和默认属性。

defaultProps / getDefaultProps()

这里会初始化一些默认的属性,通常会将固定的内容放在这个过程中进行初始化和赋值,一个控件可以利用this.props获取在这里初始化它的属性,由于组件初始化后,再次使用该组件不会调用getDefaultProps函数,所以组件自己不可以自己修改props(即:props可认为是只读的),只可由其他组件调用它时在外部修改。

在ES5里,属性类型和默认属性分别通过propTypes成员和getDefaultProps方法来实现。

//ES5
getDefaultProps: function() {
        return {
            autoPlay: false,
            maxLoops: 10,
        };
},
propTypes: {
        autoPlay: React.PropTypes.bool.isRequired,
        maxLoops: React.PropTypes.number.isRequired,
        posterFrameSrc: React.PropTypes.string.isRequired,
        videoSrc: React.PropTypes.string.isRequired,
},

在ES6里,可以统一使用static成员来实现.

//ES6
static defaultProps = {
        autoPlay: false,
        maxLoops: 10,
};  // 注意这里有分号
static propTypes = {
        autoPlay: React.PropTypes.bool.isRequired,
        maxLoops: React.PropTypes.number.isRequired,
        posterFrameSrc: React.PropTypes.string.isRequired,
        videoSrc: React.PropTypes.string.isRequired,
};  // 注意这里有分号

2. 实例化阶段

该阶段主要发生在组件类被调用(实例化)的时候。
组件类被实例化的时候,触发一系列流程:

1) constructor(props) / getInitialState()

这里是对控件的一些状态进行初始化,由于该函数不同于getDefaultProps,在以后的过程中,会再次调用,所以可以将控制控件的状态的一些变量放在这里初始化,如控件上显示的文字,可以通过this.state来获取值,通过this.setState来修改state值。

在ES5里,通过getInitialState对状态进行初始化

getInitialState: function() {
        return {
            loopsRemaining: this.props.maxLoops,
        };
},

在ES6里,通过constructor(构造器)对状态进行初始化

constructor(props){
        super(props);
        this.state = {
            loopsRemaining: this.props.maxLoops,
        };
}
2) componentWillMount()

准备加载组件。
这个调用时机是在组件创建,并初始化了状态之后,在第一次绘制 render() 之前。可以在这里做一些业务初始化操作,也可以设置组件状态。这个函数在整个生命周期中只被调用一次。
如果在这个函数里面调用setState,本次的render函数可以看到更新后的state,并且只渲染一次。

3) render()

render是一个组件必须有的方法,形式为一个函数,渲染界面,并返回JSX或其他组件来构成DOM,和Android的XML布局、WPF的XAML布局类似,只能返回一个顶级元素

4) componentDidUpdate()

调用了render方法后,组件加载成功并被成功渲染出来以后所执行的hook函数,一般会将网络请求等加载数据的操作,放在这个函数里进行,来保证不会出现UI上的错误

3. 运行(更新)阶段

该阶段主要发生在用户操作之后,或者父组件有更新的时候,此时会根据用户的操作行为,进行相应的界面结构调整。
触发的流程如下:

1) componentWillReceiveProps(nextProps)

当组件接收到新的props时,会触发该函数。在该函数中,通常可以调用setState()来完成对state的修改
输入参数 nextProps 是即将被设置的属性,旧的属性还是可以通过 this.props 来获取。在这个回调函数里面,你可以根据属性的变化,通过调用 this.setState() 来更新你的组件状态,这里调用更新状态是安全的,并不会触发额外的 render() 调用。如下:

componentWillReceiveProps: function(nextProps) {  
  this.setState({
    likesIncreasing: nextProps.likeCount > this.props.likeCount
  });
}
2) shouldComponentUpdate(nextProps, nextState)

返回布尔值(决定是否需要更新组件)

输入参数 nextProps 和上面的 componentWillReceiveProps 函数一样,nextState 表示组件即将更新的状态值。这个函数的返回值决定是否需要更新组件,如果 true 表示需要更新,继续走后面的更新流程。否者,则不更新,直接进入等待状态。
默认情况下,这个函数永远返回 true 用来保证数据变化的时候 UI 能够同步更新。在大型项目中,你可以自己重载这个函数,通过检查变化前后属性和状态,来决定 UI 是否需要更新,能有效提高应用性能。

3) componentWillUpdate(nextProps, nextState)

shouldComponentUpdate返回true或者调用forceUpdate之后,就会开始准更新组件,并调用 componentWillUpdate()。
输入参数与 shouldComponentUpdate 一样,在这个回调中,可以做一些在更新界面之前要做的事情。需要特别注意的是,在这个函数里面,你就不能使用 this.setState 来修改状态。这个函数调用之后,就会把 nextProps 和 nextState 分别设置到 this.props 和 this.state 中。紧接着这个函数,就会调用 render() 来更新界面了。

4) render()

再确定需要更新组件时,调用render,根据diff算法,渲染界面,生成需要更新的虚拟DOM数据。

5) componentDidUpdate()

虚拟DOM同步到DOM中后,执行该方法,可以在这个方法中做DOM操作。
除了首次render之后调用componentDidMount,其它render结束之后都是调用componentDidUpdate。

componentWillMount、componentDidMount和componentWillUpdate、componentDidUpdate可以对应起来。区别在于,前者只有在挂载的时候会被调用;而后者在以后的每次更新渲染之后都会被调用。
ps:绝对不要在componentWillUpdate和componentDidUpdate中调用this.setState方法,否则将导致无限循环调用。

4. 销毁阶段

该阶段主要发生组件销亡的时候,触发componentWillUnmount。当组件需要从DOM中移除的时候,通常需要做一些取消事件绑定,移除虚拟DOM中对应的组件数据结构,销毁一些无效的定时器等工作,都可以在这个方法中处理。

componentWillUnmount()

当组件要被从界面上移除的时候,就会调用 componentWillUnmount。
在这个函数中,可以做一些组件相关的清理工作,例如取消计时器、网络请求等

三、组件更新的方式(更新阶段详细)

本来是没有想要要详细写这部分内容的,不过看到另一篇文章,写得好好,就也放进来详细讲下,感谢原文作者
参考自:http://www.jianshu.com/p/4784216b8194 里的更新方式部分

更新组件(重新渲染界面)的方式有以下四种

  1. 首次渲染Initial Render,即首次加载组件
  2. 调用this.setState,状态发生改变(并不是一次setState会触发一次render,React可能会合并操作,再一次性进行render)
  3. 父组件发生更新(一般就是props发生改变,但是就算props没有改变或者父子组件之间没有数据交换也会触发render)
  4. 调用this.forceUpdate,强制更新

用图来表示这四种方式如下:


更新组件方式.png

四、总结

1. 组件生命周期总体流程图

组件的生命周期.png

2. 生命周期的回调函数总结

|生命周期 |调用次数 |能否使用 setSate() |
|: ------:|:------:|:------:|
|defaultProps / getDefaultProps| 1(全局调用一次) | 否
|constructor / getInitialState |1 |否
|componentWillMount |1 |
|render |>=1 |否
|componentDidMount |1 |
|componentWillReceiveProps |>=0 |
|shouldComponentUpdate |>=0 |否
|componentWillUpdate |>=0 |否
|componentDidUpdate |>=0 |否
|componentWillUnmount |1 |否


这篇文章参考了网上很多文章写出来的,特别是结合ES6的不同点一起写的。感觉还是挺有意义的,有不对的地方欢迎指出哈,欢迎大家提出建议。
其实如果是iOS开发人员,我觉得将组件的生命周期里的调用函数比较iOS的VC中的viewWillAppear等方法,还是挺容易理解的。(安卓应该也会有对应的概念才对)
如果感觉看了还是不太熟悉,建议自己写个demo,把所有方法都实现一次,控制台打出对应log,就一定可以更深刻的理解的!

正在写React Native的学习教程ing,是一边研究一边编写的,已有的成果如下(不断更新哈,望鼓励):
1) React Native 简介与入门
2) React Native 环境搭建和创建项目(Mac)
3) React Native 开发之IDE

4) React Native 入门项目与解析
5) React Native 相关JS和React基础
6) React Native 组件生命周期(ES6)
7) React Native 集成到原生项目(iOS)
8) React Native 与原生之间的通信(iOS)
9) React Native 封装原生UI组件(iOS)

推荐阅读更多精彩内容