最近在写react时,需要在props被改变时更新一些东西,所以使用了componentWillReceiveProps
方法,但是却发现该方法总是在各种没有改变props的情况下被调用,觉得很奇怪,遂询问我导师(我导师超厉害的!),我导师说这个方法确实有可能在props不改变的情况下被调用,所以需要在方法里手动判断一下this.props和nextProps是否相同,不相同了才执行我的更新方法。于是我抽空阅读了一下官方的文档https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/update/component_will_receive_props.html,研究了一下这个方法到底是怎么回事。下面先看一下componentDidMount()方法与componentWillReceiveProps()方法的对比。
componentDidMount()
在生命周期中只会被调用一次:
- 在最开始的render后被调用(在所有的子元素都已经被render之后,并且所有的子元素都已经被调用它们的
componentDidMount
s 之后) - 组件在被卸载后的render(这确实是一个新的生命周期)
componentWillReceiveProps()在生命周期的第一次render后不会被调用,但是会在之后的每次render中被调用 = 当父组件再次传送props。
在react的update life cycle中,不得不提到componentWillReceiveProps
方法。 该方法在props被传递给组件实例时被调用。下面详细地介绍了一下该方法。
该方法当props
发生变化时执行,初始化render
时不执行,在这个回调函数里面,你可以根据属性的变化,通过调用this.setState()
来更新你的组件状态,旧的属性还是可以通过this.props
来获取,这里调用更新状态是安全的,并不会触发额外的render
调用。
componentWillReceiveProps: function(nextProps) { this.setState({ likesIncreasing: nextProps.likeCount > this.props.likeCount }); }
传递props
该方法最典型的用法是:当新的props
被传递给组件时。例如,我们有一个Form组件和一个Person组件。Form组件有一个<input />,允许用户通过在其中输入来更改名称。 输入绑定到onChange事件并设置状态。状态值作为传递给Person组件。
Form.js
import React from 'react';
import Person './Person';
export default class Form extends React.Component {
constructor(props) {
super(props);
this.state = { name: '' } ;
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ name: event.currentTarget.value });
}
render() {
return (
<div>
<input type="text" onChange={ this.handleChange } />
<Person name={ this.state.name } />
</div>
);
}
}
当用户在输入框输入时,会使得在Person组件上调用componentWillReceiveProps(nextProps)方法传入新的prop值。我们可以通过调用this.props来获得旧的props,并且新值是传递给该方法的nextProps参数。
更新 State
那么为什么我们需要componentWillReceiveProps? 因为在这里我们能抽取出新的props并且更新内部状态,比如我们可能有一些状态,这些状态是props的计算结果,那么我们就可以在这个方法里写逻辑,使用this.setState()
来存储结果。
在componentWillReceiveProps方法里使用this.setState(),能够在调用render()之前更新状态,来对prop转换做出反应。 旧的props可以通过this.props访问。 在这个函数中调用this.setState()不会触发额外的渲染。
--https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops
Props may not change
注意:componentWillReceiveProps()方法的调用,并不意味着props的值被改变。
为什么呢?数据可能在初始渲染和后续两次更新之间发生变化,而React无法知道数据有没有变化。 因此,React需要调用componentWillReceiveProps,因为组件需要被知会新的props(即使新props恰好与旧props相同)。
-- See(A => B) !=> (B => A)
假设我们有个prop,称为data
,该prop是一个数组,如下所示:
// psuedo code
this.setState({ data: [1,2,31);">3] });
<MyComponent data={ this.state.data } />
如果在程序中对该data数组进行了push()
操作,使得data数组的内容发生了变化,但是此时数组本身(引用)并没有改变,那么react便无法判断是否该数组内部内容发生了改变,因此为了避免很多问题,也为了避免要做花费很多的深层比较,react会调用componentWillReceiveProps()方法。
因此componentWillReceiveProps()
方法允许我们去检查、判断是否进来的新props发生了变更,让我们自己去做决定。我们只需要知道:当该方法被调用时,并不意味着props肯定会不同。(Jim Sproch 的(A => B) !=> (B => A)讲得十分透彻,值得一读。)
你也许认为React应该使用更聪明的检查机制来判断props是否相等,但是这样会存在几个问题:
- 当旧的props和新的props实际上是相同的物理对象时(只更改了对象的内部值),由于引用是triple-equals-equal,所以进行相等性的检查并不能告诉我们该值是否已更改,唯一可能的解决方案是创建数据的深层拷贝,然后再进行深入比较。但对于大型数据结构(特别是具有周期的数据结构)而言,所花费的代价可能过于昂贵。
- props对象可能会包含 对函数的引用,这些函数可能会捕获闭包中的变量。 而React无法窥视这些闭包,因此React无法拷贝它们or验证相等性。
- props对象可能包含对父对象渲染过程中重新实例化的对象的引用(即不是triple-equals-equal),但在概念上相等(即相同的键和相同的值)。深度比较(代价昂贵)可以检测到这一点,除了函数,因为没有可靠的方法来比较两个函数以判断它们是否在语义上等价。
Skipping this method
与Mounting阶段的其他方法不同,并不是所有的更新阶段的方法都会每次被调用。 例如,如果update仅仅是因为一次状态更改而被触发,那么componentWillReceiveProps()将会被跳过。 回到我们上面的Form.js例子:
// ...
handleChange(event) {
return (
<div>
<input type="text" onChange={ this.handleChange } />
<Person name={ this.state.name } />
</div>
);
}
// ...
当用户在输入框中输入时,将会触发一个setState()方法。这将在Form组件和Person组件中触发Update phase。对于Form组件来说,没有收到新的props,所以componentWillReceiveProps()将会被跳过。