Component LifeCycle Methods

React Component 常用生命周期函数: getInitialState -> WillMount -> render -> DidMount -> DidUpdate(WillReceiveProps)->WillUnMount

挂载(mount):React的特点是先生成Virtual DOM,再把这个Virtual DOM在真实的DOM上画出来,这个行为就叫挂载。只有挂载后,React元素才可以在真实的DOM(也即浏览器的doument)中找到。


实际应用:

getInitialState

在组件初始化时调用一次,仅调用一次
示例代码如下:

1
2
3
4
5
getInitialState() {
return {
itemList: []
}
},

则在别的组件函数中,可以通过this.state.itemList来引用变量。

注意事项:
1.如果要改变state,请使用React的setState方法。 在本例中为:
this.setState({itemList: newList}),不要直接对state赋值,如:this.state.itemList = newList. 因为前者会触发一次render的调用(即组件重新渲染),而后者却不会,这不是正确的使用方式。

2.React并不保证setState调用后立即改变state的状态。
3.setState其实还有回调。如:

1
2
3
4
5
this.setState({
itemList: newList,
}, function() {
//do something
})

compoentWillMount

在组件挂载前调用一次,仅调用一次

我一般在这里用ajax获取数据,之后再通过setState引起组件的重新渲染。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//获取商品详情信息
getItemDetail() {
ajax({
url: shopping.api.prefix + shopping.api.itemDetail,
data: {
goodsId: goodsId
},
success: (data) => {
if(data.code != 200) alert('没有商品数据');
else {
//这里设 setState 引起组件重新render
this.setState({itemDetail: data.data});
}
}
});
},

//在WillMount里调用上面的方法
componentWillMount() {
this.getItemDetail();
},

提示:
组件在getInitialStaterender一次,如果WillMount里调用了setState, 则又会组件引起render一次。则组件一共调用render了两次。


componentDidMount

在组件挂载后调用一次,仅调用一次
此时元素节点已经在真实的DOM中生成了,可以绑定事件了。

例子:

1
2
3
4
5
6
componentDidMount() {
//查看购物车
$btns.find('.shopping-cart').on('tap', () => {
lf.appJs.doAction(`shopping-cart.html`, '购物车');
});
}

注意:
1.React.findDOMNode(this.refs.xxx) 是React自带的查找节点的方法,它查找的是虚拟DOM里的节点,也即无论组件挂载前后,它都可以查找到节点

2.用第三方类库如Zepto, 使用$(selector)来查询,它查找的是真实的DOM节点,只有在节点挂载后才可以查找到, 也只有此时添加监听事件才生效

3.虚拟DOM的节点可以被$查询包装起来,如 let $btns = $(R.findDOMNode(this.refs.btns));, 同样的,也只节点挂载后这样才有用,不然查询出来的只是个空集合。

4.注意上面的用词是节点挂载后,不是组件挂载后,组件里包含了节点,但组件DidMount了不一定节点就挂载了,这在复杂的一点的业务场景下是可能的(比如组件在DidMount利用ajax获取数据,之后再setState,则由此生成的节点要等组件触发DidUpdate事件时才算挂载成功)。

componentDidMount的陷阱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
getInitialState() {
return {
goodsIdAndNumList: [],
}
},

componentWillMount() {
ajax({
url: shopping.api.prefix + shopping.api.itemDetail,
data: {
goodsId: goodsId
},
success: (data) => {
//ajax成功返回时setState
this.setState({
goodsIdAndNumList: [{goodsId: goodsId, num: 1}]
});
}
});
}

componentDidMount() {
//code1: 在触发DidMount时使用state
let st = this.state;
console.log(st.goodsIdAndNumList);

R.findDOMNode(this.refs.createOrder).addEventListener('touchend', () => {
//code2: 在绑定的事件中使用state
console.log(this.state.goodsIdAndNumList);

//再次使用st
console.log(st.goodsIdAndNumList);
});
},

实践过程中发现,在code1code2st.goodsIdAndNumList是个空数组,而在code2this.state.goodsIdAndNumList才有值。这表明:

1.WillMount虽然先于DidMount执行,但它们默认是异步的,即后者不会因为前者未执行完成就等待。因此WillMount里使用ajax获取数据期间,DidMount就已经执行完毕了,故此时code1st并未得到更新。

2.code2st.goodsIdAndNumList仍为空数组。按道理来说st应该是引用,但事实表明,它是只是值。
对此我猜测,DidMount的时候通过this.state获取的只是组件的state的值的一个副本,当之后state再改变时,DidMount里的this.state并不会改变,因为state的改变已经反应在DidUpate函数中了。


componentDidUpdate(prevProps, prevState)

新的props或state已经同步到真实的DOM后立即调用,在初始化渲染的时候,该方法不会调用。

注意:
新的propsstate都会触发该方法,也即在子组件中该方法通常会被频繁触发。如果要在该方法中做绑定事件,最好判断清楚此次update究竟是来自props还是来自state,否则会绑定多次。不过,有一个偷懒的方法,那就是在绑定事件前先解绑,例子如下:

1
2
3
4
5
6
componentDidUpdate: function () {
var $links = $(React.findDOMNode(this.refs.list)).find('a');

$links.off();
$links.on('tap', (e) => this.toDetail(e));
},

componentWillReceiveProps(nextProps)

接收到新的props时触发;对于子组件而言,是父组件重新渲染时触发。在初始化渲染的时候,该方法不会调用。

注意:
对于子组件而言,触发该函数时,父组件传来的props有可能与上一次的props是一样的。更详细的说明,请看官方博客

死循环的经历:
父组件传了一个方法给子组件,该方法调用了setState,会引起父组件的重新渲染,也会触发componentWillReceiveProps方法。
而我在componentWillReceiveProps方法中未做nextPropsthis.props的判断,直接就调用父组件传过来的方法,引起父组重新渲染,重新触发子组件的componentWillReceiveProps,再次调用父组件传过来的方法,于是就死循环了。


render

通过React.createClass()extends React.Component创建的组件必须包含render方法。

render方法里一般根据stateprops来渲染组件。render里面可以传变量,如要做一些逻辑判断,你会发现三元表达式大有用场,更多的例子,请看官方文档

例子:

1
2
3
4
5
6
7
8
9
10
11
12
render() {
return (
<ul className={"order-list " + this.state.class}>
{this.state.orderList}
{
this.state.noData ?
<NoData data={{span_text:
flag == ORDER_LIST.PENDING ? '还没有待支付订单噢~' : '还没有订单噢~'}}/> : ''
}
</ul>
)
}

注意:
1.render 方法里不允许setState,原因很简单,setState会触发新的render,允许你setState,那就死循环啦。
2.父组件重新render一次,会引起下面的全部子组件重新render一次。

shouldComponentUpdate(nextProps, nextState)

该方法执行于render之前, 必须要有一个返回值. 返回true, 则会执行render, 返回false则不执行render.
如果没有返回值, 则相当于返回undefined, 会报错

不想每次state的改变都引起组件的重新渲染时, 可以自定义此方法

参考资料

Fork me on GitHub