合理使用React的生命周期

大家在写React代码的时候也就是在React的生命周期函数中编写代码,有的时候我们在两个生命周期中编写代码可能得到的是相同的结果,但是对于生命周期的使用是有最佳实践的。

特别是在React v16.3之后引入了异步渲染的概念,这个时候我们如何使用生命周期就变得更加重要了,首先我们先来看看v16.3之前的生命周期

React v16.3以前的生命周期

react hocks beforev16.3

创建阶段

1
2
3
4
5
// 构造函数
constructor(props) {
super(props)
this.state = { counter: 0 }
}

构造函数阶段,只调用一次,接收到传入的props,需要手动调用super(props)

在这个阶段我们对组件的state数据进行初始化

1
2
3
4
5
render() {
return (
...
)
}

render阶段 这个阶段我们渲染UI

1
2
3
4
5
6
7
componentDidMount() {
const container = this.DomContainer
const containerHeight = container.clientHeight
this.setState({
containerHeight
})
}

UI渲染完毕的阶段,只调用一次,在这个生命周期开始DOM才被创建,一些jquery库需要在这里初始化,如果需要计算DOM的高度来初始化,那么也应该在这里进行初始化

更新阶段

更新分为两种:一种是父组件传入的props变化,或者组件调用setState()

当props发生变化时进入

1
2
componentWillReceiveProps(nextProps) {
}

在这里我们可以更新组件的state,但是最好是根据组件的props计算后去渲染UI,因为这样可以保证单一数据源又避免维护一份数据产生额外的开销

1
2
shouldComponentUpdate(nextProps, nextState) {
}

返回一个boolean值,表示是否应该更新,默认返回true,返回false时不再执行下面的生命周期

1
2
3
4
5
6
7
8
9
componentWillUpdate(nextProps, nextState) {
}
render() {
return (
...
)
}
componentDidUpdate() {
}

render声明周期前后分别是componentWillUpdate和componentDIdUpdate

更新的第二种是组件调用forceUpdate,这时候会直接进入componentWillUpdate

卸载阶段

1
2
componentWillUnmount() {
}

组件在页面卸载之前进入componentWillUnmount生命周期,在这里我们可以对需要清理的资源资源进行释放,比如:清除计时器

Reactv16.3开始的生命周期

react hocks afterv16.3

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

这些生命周期方法经常被错误的使用,当开启异步渲染的时候可能存在安全隐患

所以16.3版本开始 这些生命周期将增加UNSAFE_开头的别名

17.0版本开始将删除这些生命周期 只有UNSAFE_开头的生命周期可以使用

新增俩个生命周期getDerivedStateFromProps和getSnapshotBeforeUpdate

1
2
getDerivedStateFromProps(nextProps, prevState) {
}

这个生命周期替换了componentWillReceiveProps的所有情况,接收到nextProps和prevState,返回一个state对象merge到原来的state

1
2
getSnapshotBeforeUpdate(prevProps, prevState) {
}

这个生命周期替换了componentWillUpdate,返回的对象将作为第三个参数传给componentDidUpdate

除了这俩个生命周期的变化,还提出了三个阶段的概念,render阶段,pre-commit阶段和commit阶段,由于要支持异步渲染,所以组件的生命周期可能由于优先级更高的事件触发而终止,
在render阶段我们可以终止组件继续渲染。
在pre-commit阶段我们可以对dom的数据进行读取。
在commit阶段我们可以使用dom,执行副作用操作。

  1. 添加事件监听
    componentWillMount 和 componentWillUnmount 不是一对生命周期
    只有调用了componentDidMount 才一定会调用 componentWillUnmount
    所以如果在componentWillMount中做了一些比如注册监听的工作
    由于component 在没有完成componentDidMount的时候可能会被打断
    所以也不会执行 componentWillUnmount 中的清理工作 It can leak
    所以我们应该把注册监听的操作放到componentDidMount中执行
    这样就保证我们在componentWillUnmount中的清理工作一定会被执行
  2. 获取外部数据
    在componentWillMount中大家可能会去获取外部数据
    使得数据会再componentDIdMount的时候正确的渲染
    但实际上这是做不到的
    在执行完componentWillMount之后会立刻执行componentDidMount
    这是一个同步执行的过程 而从接口获取数据是一个异步的过程
    所以提前到componentWillMount中执行是没有意义的

  3. 初始化数据
    componentWillMount 中初始化数据的操作应该迁移到constructor中

  4. componentWIllUpdate 和 componentDidUpdate
    当组件更新时需要产生副作用 使用componentDidUpdate 而不是componentWillUpdate
    在异步模式下 多次willUpdate可能只会触发一次真正的更新所以在willUpdate中产生副作用可能达不到你想要的预期

  5. getSnapshotBeforeUpdate(prevProps, prevState)
    使用componentWillUpdate读取dom属性对于异步渲染来说
    componentWillUpdate 和 render 属于 render 生命周期
    与 componentDidUpdate 之间存在延迟
    如果在这个时间内用户改变了dom 存储的数据将会失效
    解决的方法就是使用getSnapshotBeforeUpdate方法