React - 生命周期

对 React 中 生命周期 的一些理解。

v16.0 前的生命周期

组件挂载: componentWillMount -> render -> componentDidMount

组件更新(state 变化): shoudlComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

组件更新(props 变化): componentWillReceiveProps -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

组件销毁: componentWillUnmount

Initialization (初始化阶段)

constructor

触发时机:

子组件实例化时。

解析:

实际上将 constructor 作为生命周期钩子这一说法并不确切。constructor 作为 class(ES6) 中的 构造方法 ,通过 new 关键字创建对象实例时会自动调用,用于返回实例对象(this)。React 的生命周期定义在 Component 这个基类中,在执行 constructor() 后组件才会继承 Component 基类,才可以使用这些生命周期方法。所以 hooks 范式中没有生命周期,因为没有使用对象(返回函数组件)。

使用场景:

  1. super(props) 用于调用父组件(基类)的构造方法,并将父组件的 props 注入给子组件。

  2. this.state 中可以初始化 state 内容。

1
2
3
4
5
6
7
8
9
class User extends Component {
constructor(props) {
super(props);
this.state = {
name: 'anonymous',
gender: 'male'
}
}
}

Mounting (挂载阶段)

该阶段在组件第一次挂载时触发,只会执行一次。

componentWillMount()

触发时机:

组件挂在到 DOM 执行,只会执行一次。

解析:

不建议 在该阶段请求网络数据,因为一般网络请求都是以 异步 的方式进行,不能保证在 render() 前就完成,可能还没获取到数据就已经执行了渲染操作。至于初始化 state,可以,但没必要,因为放到 constructor 更直观且主流。

使用场景:

不过 componentWillMount 也不是一无是处,在服务端渲染(SSR)中生命周期不全,componentWillMount 是唯一的生命周期钩子。

render()

第一次渲染。

componentDidMount()

触发时机:

组件挂在到 DOM 执行,只会触发一次。

解析:

在该阶段执行网络请求最合理,因为这时候已经完成渲染,有足够的时间去执行 异步请求,能够保证数据安全,且获取数据后一般会进行 setState() 操作,触发重渲染。

使用场景:

  1. 执行网络请求,获取远程数据。

  2. 使用 setState() 保存状态,触发重渲染。

  3. 使用 ref 操作 DOM,因为这时候组件已经挂载到 DOM 上了。

1
2
3
4
async componentDidMount() {
const res = await getUserInfo();
this.setState({ userInfo: res.data });
}

Updation (更新阶段)

该阶段在组件 stateprops 改变时触发,可能触发多次。

componentWillReceiveProps(nextProps)

触发时机:

组件挂载完成后,接收到新的 props 时触发,此时还没有触发重渲染。

解析:

可以通过 this.props 获取之前的属性,通过 nextProps 获取之后的属性,进行对比或其他操作。由于此时还没有触发重渲染,所以可以将一些 网络请求 放到这里执行,减轻负担,达到一定的优化效果。

使用场景:

  1. 对比 this.propsnextProps,执行后续操作。

  2. 子组件发起 网络请求

shouldComponentUpdate(nextProps, nextState)

触发时机:

组件挂载完成后,在接收到新的 stateprops 时触发。

解析:

可以通过对比 this.statenextState,或 this.propsnextProps 是否发生变化,返回一个 boolean 值决定是否重渲染(返回 true 表示本次允许重渲染,返回 false 表示本次不允许重渲染),从而达到性能优化的目的。建议只进行浅比较(不递归比较嵌套对象),因为比较层级过多的话消耗掉会大于重渲染,得不偿失。在 React 15.3 后引入了 PureComponent,代替了 shouldComponentUpdate 自动进行 浅比较 判断 stateprops 是否更新,从而优化性能,不过它只对子组件生效。

使用场景:

判断 stateprops 是否更新,从而决定是否渲染,达到性能优化的目的。

1
2
3
shouldComponentUpdate(nextProps, nextState) {
return nextState.user !== this.state.user
}
componentWillUpdate(nextProps, nextState)

触发时机:

stateprops 发生变化时,重渲染 触发。

解析:

不能 在这个生命周期调用 setState(),因为每次调用 setState() 触发重渲染都会经过这一生命周期,周而复始引起死循环。另外,若是 shouldComponentUpdate 返回了 fasle 阻止了重渲染,则不会触发 componentWillUpdate

使用场景:

可以做一些动画或 DOM 的初始化。

render()

重渲染。

componentDidUpdate(prevProps, prevState)

触发时机:

组件更新,重渲染 触发。

解析:

若是 shouldComponentUpdate 返回了 fasle 阻止了重渲染,也不会执行到这一步。

使用场景:

  1. 使用 ref 操作 DOM,因为这时候组件已经重渲染,且挂载到 DOM 上了。

解析:

Unmounting (卸载阶段)

该阶段在组件销毁时触发,只会触发一次。

componentWillUnmount()

触发时机:

在组件卸载之前执行。

解析:

由于是单页面应用(SPA),在路由跳转后,页面内的一些 事件订阅定时操作 并不会主动销毁,任然留在内存中继续执行。此时会引发内存泄漏,影响性能;此外,由于组件已经销毁,this 已经解除链接,获取不到相关引用变量, 会造成 underfined 报错。

使用场景:

执行一些必要的清理操作,例如 clearInterval()clearTimeout()等。

1
2
3
componentWillUnmount() {
this.timer && clearTimeout(this.timer);
}

v16.0 后的生命周期

在 React 16.0 之后,废除了 componentWillMountcomponentWillUpdatecomponentWillReceiveProps 这三个生命周期,将在 17.0 之后移除。因为引入了 fiber 架构,render() 前一阶段可以被打断并且执行多次,导致这些生命周期变得不可控。不过为了平滑升级,暂时可以使用 UNSAFE_componentWillMountUNSAFE_componentWillUpdateUNSAFE_componentWillReceiveProps 代替。

取而代之引入了两个新的生命周期:getDerivedStateFromPropsgetSnapshotBeforeUpdate

组件挂载: getDerivedStateFromProps -> render -> componentDidMount

组件更新: getDerivedStateFromProps -> shoudlComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate

组件销毁: componentWillUnmount

static getDerivedStateFromProps(nextProps, prevState)

触发时机:

每次渲染前调用。(包括首次渲染和重渲染)

解析:

首先这是一个 static 方法,不能使用 this 访问对象属性,所以也就不能直接使用 this.setState() 方法了。该方法接受两个参数,nextProps 指接收的新属性,prevState 指当前状态,最后返回一个对象来更新当前 state 状态,如果不需要渲染则返回一个 null。本质上就是讲传入的 props 映射到 state

使用场景:

替代之前的 componentWillReceiveProps 等生命周期。

1
2
3
4
5
6
7
8
9
static getDerivedStateFromProps(nextProps, prevState) {
const {username} = nextProps;
if (username !== prevState.username) {
return {
username,
};
}
return null;
}
getSnapshotBeforeUpdate(prevProps, prevState)

触发时机:

在渲染后,componentDidUpdate 前调用。

解析:

该方法接受两个参数,prevProps 指当前属性,prevState 指当前状态,最后返回一个对象传递给 componentDidUpdate

使用场景:

用于替代 componentWillUpdate 等生命周期。

参考资料

React渲染原理

react如何通过shouldComponentUpdate来减少重复渲染

我对 React v16.4 生命周期的理解

查看评论