为什么要升级你的代码?
这一次生命周期的升级非常重要,由于
react
官方即将发布的异步渲染机制,也就是React Fiber是什么.造成原来只会调用一次的生命周期会有可能多次调用。甚至是调用到一半然后作废重新调用。所以在原来的reader
之前的生命周期都不在安全。如果你在render
之前的生命周期中进行副作用的操作,如异步请求接口,订阅,耗时操作等其他的,都有可能出现意想不到的bug
。所以我们要把这些原来的操作放到render
完成之后的生命周期,这样可以保证我们进行的副作用只会调用一次,下面我们来说一下之前我们的一些操作怎么升级到新的生命周期里。那么现在开始吧。
关于 componentWillMount
的
初始化操作
有的人可能喜欢在componentWillMount
进行初始化操作。例如:
class ExampleComponent extends React.Component {
state = {};
componentWillMount() {
this.setState({
currentColor: this.props.defaultColor,
palette: 'rgb',
});
}
}
这种操作最简单的方式就是把它放到构造函数里或者属性初始值设定项中:
// After
class ExampleComponent extends React.Component {
state = {
currentColor: this.props.defaultColor,
palette: 'rgb',
};
}
//或者:
class ExampleComponent extends React.Component {
constructor(props) {
super(props);
currentColor: props.defaultColor,
palette: 'rgb',
};
}
获取外部数据--如异步调用的
有在componentWillMount
生命周期中进行异步调用获取数据的,例如:
// Before
class ExampleComponent extends React.Component {
state = {
externalData: null,
};
componentWillMount() {
this._asyncRequest = loadMyAsyncData().then(
externalData => {
this._asyncRequest = null;
this.setState({externalData});
}
);
}
componentWillUnmount() {
if (this._asyncRequest) {
this._asyncRequest.cancel();
}
}
render() {
if (this.state.externalData === null) {
// Render loading state ...
} else {
// Render real UI ...
}
}
}
在即将到来的异步渲染模式里,有可能会多次调用这个生命周期,如果你在这里进行接口请求,就有可能多次调用接口。所以这里的建议是把请求接口的操作放到渲染完毕以后的componentDidMount
里。
// After
class ExampleComponent extends React.Component {
state = {
externalData: null,
};
componentDidMount() {
this._asyncRequest = loadMyAsyncData().then(
externalData => {
this._asyncRequest = null;
this.setState({externalData});
}
);
}
componentWillUnmount() {
if (this._asyncRequest) {
this._asyncRequest.cancel();
}
}
render() {
if (this.state.externalData === null) {
// Render loading state ...
} else {
// Render real UI ...
}
}
}
很多人以为componentWillMount
和componentWillUnmount
是配对的。但是其实并不是。反而是componentDidMount
和componentWillUnmount
是一对。他们特别适合在订阅/取消的操作
。例如:setTimeout、setInterval
;上面例子中请求接口/取消接口
等。我们这里举一个setInterval
的例子:
class Clock extends React.Component {
constructor(props){
super(props)
this.state = getClockTime();
}
componentDidMount(){
this.ticking = setInterval(()=>{
this.setState(getClockTime();
},1000);
}
componentWillUnmount(){
clearInterval(this.ticking);
}
render(){
...
}
}
关于componentWillReceiveProps
的升级
这个方法的升级是重中之重。在以前我们通过该方法来实现更新state
。以达到受控组件的目的,父组件可以控制子组件的更新,获取数据,重新渲染等的操作。在使用新的getDerivedStateFromProps
方法来代替原来的componentWillReceiveProps
方法时,原来的一些写法也不在适用。
当新的props
到来时更新state
下面是使用componentWillReceiverProps
来实现基于新的props
来更新state
.
class ExampleComponent extends React.Component {
state = {
isScrollingDown: false,
};
componentWillReceiveProps(nextProps) {
if (this.props.currentRow !== nextProps.currentRow) {
this.setState({
isScrollingDown:
nextProps.currentRow > this.props.currentRow,
});
}
}
}
虽然上面的代码并没有问题,但是componentWillReceiveProps
经常被错误的使用。因此该方法将被废弃!
从版本16.3
开始,我们推荐使用stattic getDerivedStateFromProps(props,state)
来实现基于props
对state
的更新。即将废弃的三个方法的功能都会被该方法替代。也即是说,组件在挂载时,更新props
时,更新state
时,forceUpdate
时,都会调用该方法。
换一句话说。只要你render
就会调用该方法。由于该方法是一个静态方法,无法拿到该组件的实例。
因为其被频繁的调用。同样在这个方法里也不适合进行耗时的操作。
我们看一下官方给出的例子:
class ExampleComponent extends React.Component {
state = {
isScrollingDown:false,
lastRow:null;
}
static getDerivedStateFromProps(props,state){
if(props.currentRow !== state.lastRow){
return {
isScrollingDown:props.currentRow > state.lastRow,
lastRow:props.currentRow,
}
}
return null;
}
}
在上面的例子中:static getDerivedStateFromProps(props,state)
由于是静态方法,因为拿不到组件的实例。所以在这里也就不能通过this.props.currentRow
来拿到当前实例对象中保存的currentRow
数据。
在上面的例子中是通过把镜像数据保存到state
里,然后在新的props
到来时进行比较来达到同样的效果的。
当新的props
到来时,进行异步接口请求.
我们有时候也会有这种需求。当新的props
更新时,我们需要重新获取数据来更新页面,例如:
class ExampleComponent extends React.Component {
state = {
externalData:null,
}
componentDidMount(){
this._loadAsyncData(this.props.id);
}
componentWillReceiveProps(nextProps){
if(nextProps.id !== this.props.id){
this.setState({externalData:null});
this._loadAsyncData(nextProps.id)
}
}
componentWillUnmount(){
if(this._asyncRequest){
this._asyncRequest.cancel();
}
}
render(){
if(this.state.externalData === null){
//...
}else {
//...
}
}
_laodAsyncData(id){
this._asyncRequest = loadMyAsyncData(id).then(
externalData => {
this._asyncRequest = null;
this.setState({externalData});
}
);
}
}
在上面的例子中,我们获取数据的依据是props
的id
。所以我们需要在componentWillReceiveProps
中监听id
的变化。当变化发生时触发异步获取数据。
而这里的代码升级可以说秉承着一个原则,就是凡是异步获取,或者有副作用的操作,通通扔到生命周期的后面去,也就是render
完以后,以保证数据只执行一次。
在这里就是把数据获取放到componentDidUpdate
中。而我们的getDerivedStateFromProps
生命周期干嘛呢?
更新props.id
的数据到state
里。置空externalData
为null
。我们通过判断externalData
来获取数据。
class ExampleComponent extends React.Component {
state = {
externalData:null,
}
static getDerivedStateFromProps(props,state){
//我们把props.id的值保存为state.prevID。当新的props.id到来时,我们用来做比较
// 清除原来的数据,这样我们可以基于此来判断是否需要重新获取数据
if(props.id 1== state.prevID){
return {
externalData:null,
prevID:props.id
}
}
return null;
}
componentDidMount(){
this._loadAsyncData(this.props.id);
}
//在更新完成以后,通过externalData是否为空来判断是否需要更新数据
componentDidUpdate(prevProps,prevState){
if(this.state.externalData === null){
this._loadAsyncData(this.props.id);
}
}
componentWillUnmount(){
if(this._asyncRequest){
this._asyncRequest.cancel();
}
}
render(){
if(this.state.externalData === null){
//...
}else {
//...
}
}
_laodAsyncData(id){
this._asyncRequest = loadMyAsyncData(id).then(
externalData => {
this._asyncRequest = null;
this.setState({externalData});
}
);
}
}
现在我们总结一下getDerivedStateFromProps
和componentWillReceiveProps
使用上的差异:
角标 | componentWillReceiveProps |
getDerivedStateFromProps |
---|---|---|
如何确定是否更新state
|
if(nextProps.id !== this.props.id){} |
把镜像保存到state 里。通过保存在state 里的镜像来进行比较判断if(props.id 1== state.prevID){
|
如何确定是否需要重新获取数据 | 通过 if(nextProps.id !== this.props.id){} 直接就可以知道需要重新获取数据 |
通过保存在state 里的镜像来进行比较判断,在上面的例子是通过把数据置空,我们也可以设置一个loading 为true 。然后在componentDidUpdate(prevProps,prevState){} 判断来触发获取数据的操作。 |
想比较于原来的做法确实要繁琐一点。
关于componentWillUpdate
关于这个的生命周期,我在实际开发中基本没有用到过。官网例子中举了一个render
之前获取DOM
属性的操作。
然后使用新的生命周期getSnapshotBeforeUpdate
来取代一下:
class ScrollingList extends React.Component {
listRef = null;
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
return (
this.listRef.scrollHeight - this.listRef.scrollTop
);
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
this.listRef.scrollTop =
this.listRef.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.setListRef}>
{/* ...contents... */}
</div>
);
}
setListRef = ref => {
this.listRef = ref;
};
}
通过getSnapshotBeforeUpdate
拿到数据以后,把数据返回,然后在componentDidUpdate(prevProps, prevState, snapshot)
的第三个参数拿到我们之前返回的数据,然后进行一些操作。
到这里就写完了。新的生命周期getDerivedStateFromProps
我把它放在了关于componentWillReceiveProps的升级
章节。
over....
参考学习的文章:
网友评论