过时的组件生命周期往往会带来不安全的编码实践,具体函数:componentWillMount
、 componentWillReceiveProps
、 componentWillUpdate
。
旧生命周期
componentWillMount
componentWillMount 此钩子在页面初始化 render 之前会执行一次或多次(async rendering)。
问题 1: 在此生命周期进行请求数据来解决首页白屏,并没有解决。在异步调用 api 时,浏览器进行其他工作,但是对于 React 组件,并不会等待 componentWillMount
钩子的任何事件,而是继续 render。
问题 2: 当在父子组件传递属性时,父组件传递的属性需要异步获得,那么在子组件的 componentWillMount
和 componentDidMount
处理会有问题,因为此时父组件中对应的属性可能还没有完整获取,因此在子组件的 componentDidUpdate
中处理
问题 3: 利用 componentWillMount
和 componentWillUnmount
对称性来进行事件的订阅取消,但是在开启 async rendering,在 render 函数之前的所有函数,都可能被多次执行,并且组件的渲染很有可能会被其他事务所打断,导致 componentWillUnmount
不会被调用。而 componentDidMount
就不存在这个问题, componentDidMount
被调用后, componentWillUnmount
一定会调用。
componentWillReceiveProps(nextProps, nextContext)
componentWillReceiveProps
不会再 render 期间执行,当父组件导致子组件更新的时候,不管 props 或 context 是否变化,都会触发。
旧的 componentWillReceiveProps
和新的 getDerivedStateFromProps
方法都会给组件增加明显的复杂性。这通常会导致 bug。
componentWillUpdate
在异步模式下使用 componentWillUpdate
都是不安全的,因为外部回调可能会在一次更新中被多次调用。相反,应该使用 componentDidUpdate
生命周期,因为它保证每次更新只调用一次。
旧生命周期测试代码
import React from 'react';
import ReactDOM from 'react-dom';
class Son extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
console.log('constructor');
}
componentWillMount() {
console.log('componentWillMount');
}
componentDidMount() {
console.log('componentDidMount');
}
componentWillUnmount() {
console.log('componentWillUnmount');
}
componentWillReceiveProps(nextProps, nextContext) {
console.log('componentWillReceiveProps nextProps', nextProps);
console.log('componentWillReceiveProps nextContext', nextContext);
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
console.log('shouldComponentUpdate nextProps', nextProps);
console.log('shouldComponentUpdate nextState', nextState);
console.log('shouldComponentUpdate nextContext', nextContext);
return true;
}
componentWillUpdate() {
console.log('componentWillUpdate');
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
add = () => {
this.setState({
count: this.state.count + 1
});
};
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('root'));
};
update = () => {
this.forceUpdate();
};
render() {
console.log('render');
return (
<fieldset>
<legend>count: {this.state.count}</legend>
<button onClick={this.add}>Count + 1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.update}>强制更新组件</button>
</fieldset>
);
}
}
class Parent extends React.Component {
componentWillUnmount() {
console.log('Parent - componentWillUnmount');
}
render() {
return (
<div>
<p>父组件</p>
<Son />
</div>
);
}
}
export default Parent;
新生命周期
getDerivedStateFromProps(nextProps, nextState)
在组件实例化的时候,替代了
compontentWillMount()
在接受到新的 props 或者 state 更新时,该方法替换了compontentWillReceiveProps
和componentWillUpdate()
- 该方法是会在每次组件被重新渲染前调用,这就意味着无论 父组件更新, props 变换,setState()执行,都会触发
- 该组件存在返回值 null 或者 一个对象
- 当需要更新状态时,需要返回一个对象,这个对象的内容,会派生到 state 中,如果 state 中已经包含某属性,则覆盖原来的属性
- 当不需要更新状态时,需要返回 null
注意:
componentWillReceiveProps
和getDerivedStateFromProps
同时存在,控制台报错。- 由于
getDerivedStateFromProps
会在 setState() 后被调用, 并且它的返回值会被用于更新数据。这意味着你会在 setState() 之后触发getDerivedStateFromProps
, 然后可能意外地再次 setState(). getDerivedStateFromProps
(nextProps) 函数中的第一个位置参数未必是 "新" 的 props. 在组件内调用了 setState() 时,getDerivedStateFromProps
会被调用. 但是此时的组件其实并没有获得 "新" 的 props, 是的, 这个 nextProps 的值和原来的 props 是一样的. 这就导致了我们在使用getDerivedStateFromProps
时, 必须添加很多逻辑判断语句来处理 props 上的更新和 state 上的更新, 避免意外地返回了一个 Updater 再次更新数据, 导致数据异常.
getSnapshotBeforeUpdate(prevProps, prevState)
与 componentWillUpdate
不同,该生命周期是在 rerender
之后触发,拿到的是上次(更新之前的数据),但是可以获取最新的 DOM 结构 和 ref 引用,一般这里是没用的,但是需要一个返回值,这个返回值是一个对象,随后这个返回值在会被 componentDidUpdate(prevProps, prevState, snapshot)
第三个参数接收,然后在 compontentDidUpdate
更新状态,而不是直接在该生命周期直接更新状态。
注意:
1) 将原先写在 componentWillUpdate 中的回调迁移至 componentDidUpdate,将以前放在 componenWillReceiveProps 中的异步网络请求放在 DidUpdate 中。
2)在 componentDidUpdate 中使用 setState 一定要进行 if 判断, 判断 prevProps, prevState 和 this.state 之间的一个数据变化, 通过判断变化是否产生, 来决定是否调用 if 语句里面的 state, 尽管在 didUpdate 里面调用了 state, 会再一次进来 didUpdate , 但是这一次 if 语句里面的条件不成立, 就不会再次调用 setState 造成死循环了。
新生命周期测试代码
import React, { Component } from 'react';
class Son extends Component {
state = {
msg: '这是子组件'
};
static getDerivedStateFromProps(nextProps, prevState) {
console.group('子组件 - shouldComponentUpdate');
console.log(nextProps);
console.log(prevState);
console.groupEnd('子组件 - shouldComponentUpdate');
return nextProps.num > 0.5 ? { num: nextProps.num } : null;
}
render() {
return (
<div style={{ margin: 10, padding: 10, border: '1px solid red' }}>
<p>{this.state.msg}</p>
<p>子组件接收父组件的 num 值(子组件大于 0.5 时更新):{this.state.num}</p>
</div>
);
}
}
class Fa extends Component {
state = {
name: '这是父组件',
num: '这是父组件给子组件传递的值',
arr: [1, 2, 3, 4, 5]
};
static getDerivedStateFromProps(nextProps, prevState) {
console.group('父组件 - getDerivedStateFromProps');
console.log(nextProps);
console.log(prevState);
console.groupEnd('父组件 - getDerivedStateFromProps');
/**
* 这里返回 null 或者 一个对象
* 当需要更新状态时,需要返回一个对象,这个对象的内容,会添加到 state 中,如果 state 中已经包含某属性,则覆盖原来的属性
* 当不需要更新状态时,需要返回 null
*/
return {
name: 'getDerivedStateFromProps 覆盖原name属性',
notDefine: '未在state声明,此时添加到 state'
};
}
componentDidMount() {
console.group('父组件 - componentDidMount');
console.log(this.state);
console.groupEnd('父组件 - componentDidMount');
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
console.group('父组件 - shouldComponentUpdate');
console.log(nextProps);
console.log(nextState);
console.log(nextContext);
console.groupEnd('父组件 - shouldComponentUpdate');
// 判断是否 rerender
return nextState;
}
/**
* 获取上次的 props 和 state 数据
* 并且返回 null 或者 object,然后将返回值作为 snapshot,传递给 componentDidUpdate第三个参数
*/
getSnapshotBeforeUpdate(prevProps, prevState) {
console.group('父组件 - getSnapshotBeforeUpdate');
console.log(prevProps);
console.log(prevState);
console.groupEnd('父组件 - getSnapshotBeforeUpdate');
return {
msg: 'from getSnapshotBeforeUpdate'
};
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.group('父组件 - componentDidUpdate');
console.log(prevProps);
console.log(prevState);
console.log(snapshot);
console.groupEnd('父组件 - componentDidUpdate');
}
changeStr = () => {
this.setState({
num: Math.random()
});
};
render() {
const { name, num, arr } = this.state;
return (
<div style={{ margin: 10, padding: 10, border: '1px solid blue' }}>
{name}:<button onClick={this.changeStr.bind(this)}>改变 num 的值</button>
<p>这是父组件的 num 值: {num}</p>
<Son
num={num}
arr={arr}
></Son>
</div>
);
}
}
export default Fa;