聊一聊 React 生命周期

631 阅读7分钟

读完本文希望能对React的组件生命周期有一定的了解,编写React代码的时候能够更加得心应手 ^_^。

生命周期

生命周期函数说白了就是让我们在一个组件的各个阶段都提供一些钩子函数来让开发者在合适的时间点可以 介入并进行一些操作,比如初始化的时候我们应该初始化组件相关的状态和变量,组件要销毁时,我们应该 把一些数据结构销毁掉以节约内存,防止后台任务一直运行。

React 16的生命周期,总的来说React组件的生命周期分为三个部分: 装载期间(Mounting) ,更新期 间(Updating) 和卸载期间(Unmounting) ,React16多出来一个componentDidCatch() 函数用于 捕捉错误。

知道什么时候去使用哪些生命周期函数对于掌握和理解React是非常重要的,这些生命周期函数 有一定的规律,比如在某件事情发生之前调用的会用xxxWillxxx,而在这之后发生的会用xxxDidxxx。 接下来我们就这三个阶段分别介绍一下各个生命周期函数,详细的生命周期函数解释可以看官方文档 reactjs.org/docs/react-…

装载期间

 组件被实例化并挂载在到DOM树这一过程称为装载,在装载期调用的生命周期函数依次为
 constructor()
 getDerivedStateFromProps()
 render()
 componentDidMount()

还有一些函数比如getDefaultProps, getInitialState等是在不是用ES6的class创建组件而是用createReactClass函数创建函数时暴露的方法,分别用于定义属性和设置初始状态,详见reactjs.org/docs/react-…,这里我们不再赘述。 通常推荐使用继承组件类的方式进行组件创建,即class Analysis extends Component{}

  • constructor(props)

    构造函数,和java class的构造函数一样,用于初始化这个组件的一些状态和操作,如果是通过继承React.Component子类来创建React的组件的,那么应当首先调用super(props) 初始化父类。

    在contructor函数中,可以初始化state,比如this.state = {xxx};,不要在构造函数中使用setState()函数,强行使用的话React会报错。其可以在构造函数中进行函数bind,如:

    一个示例contructor实现如下:

    如果不需要初始化状态也不需要绑定handle函数的this,那么可以不实现constructor函数,由默认实现代替。

  • getDerivedStateFromProps()

    这个函数会在render函数被调用之前调用,包括第一次的初始化组件以及后续的更新过程中,每次接收新的props之后都会返回一个对象作为新的state,返回null则说明不需要更新state。

    该方法主要用来替代componentWillReceiveProps方法,willReceiveProps经常被误用,导致了一些问题,因此在新版本中被标记为unsafe。

    componentWillReceiveProps的常见用法如下,根据传进来的属性值判断是否要load新的数据。

    但这个方法的一个问题是外部组件多次频繁更新传入多次不同的 props,而该组件将这些更新 batch 后仅仅触发单次自己的更新,这种写法会导致不必要的异步请求,相比下来getDerivedStateFromProps配合componentDidUpdate的写法如下:

    这种方式只在更新触发后请求数据,相比下来更节省资源。

  • render()

    该方法在一个React组件中是必须实现的,可以看成是一个java interface的接口

    这是React组件的核心方法,用于根据状态state和属性props渲染一个React组件。我们应该保持该方法的纯洁性,这会让我们的组件更易于理解,只要state和props不变,每次调用render返回的结果应当相同,所以请不要在render方法中改变组件状态,也不要在在这个方法中和浏览器直接交互。

  • componentDidMount()

    componentDidMount方法会在render方法之后立即被调用,该方法在整个React生命周期中只会被调用一次。React的组件树是一个树形结构,此时可以认为这个组件以及他下面的所有子组件都已经渲染完了,所以在这个方法中可以调用和真实DOM相关的操作了。

    有些组件的启动工作是依赖 DOM 的,例如动画的启动,而 componentWillMount 的时候组件还没挂载完成,所以没法进行这些启动工作,这时候就可以把这些操作放在 componentDidMount 当中。

    我们推荐可以在这个函数中发送异步请求,在回调函数中调用setState()设置state,等数据到达后触发重新渲染。但注意尽量不要在这个函数中直接调用setState()设置状态,这会触发一次额外的重新渲染,可能造成性能问题。

    下面的代码演示了如何在componentDidMount加载数据并设置状态:

更新期间

 当组件的状态或属性变化时会触发更新,更新过程中会依次调用以下方法:
 getDerivedStateFromProps()  上文已描述,不赘述
 componentWillUpdate()
 render()
 getSnapshotBeforeUpdate()
 componentDidUpdate()
  • shouldComponentUpdate(nextProps, nextState)

    可以用这个方法来告诉React是否要进行下一次render(),默认这个函数放回true,即每次更新状态和属性的时候都进行组件更新。注意这个函数如果返回false并不会导致子组件也不更新。

    这个钩子函数一般不需要实现, 如果组件性能比较差或者渲染比较耗时,可以考虑使React.PureComponent 重新实现该组件,PureComponent默认实现了一个版本的shouldComponentUpdate会进行state和props的比较。当然如果有自信,可以自己实现比较nextProps和nextState是否发生了改变。

  • getSnapshotBeforeUpdate()

    该方法的触发时间为update发生的时候,在render之后dom渲染之前返回一个值,作为componentDidUpdate的第三个参数。该函数与 componentDidUpdate 一起使用可以取代 componentWillUpdate 的所有功能,比如以下是官方的例子:

  • componentDidUpdate(prevProps, prevState, snapshot)

    该方法会在更新完成后被立即调用,可以在这个方法中进行DOM操作,或者做一些异步调用。这个和首次装载过程后调用componentDidMount是类似的,不一样的是可能需要判断下属性是否变化了再发起网络请求,如:

卸载期间

卸载期间是指组件被从DOM树中移除时,调用的相关方法为:
componentWillUnmount()
  • componentWillUnmount()

    该方法会在组件被卸载之前被调用,如果学过C++,那么这玩意和析构函数差不多,在方法里清理内存之类的。如上所述,可以在这个函数中进行相关清理工作,比如删除定时器之类的。

    下面给个示例代码:

错误捕获

 React16中新增了一个生命周期函数:
 componentDidCatch()
  • componentDidCatch(error, info)

    在react组件中如果产生的错误没有被被捕获会被抛给上层组件,如果上层也不处理的话就会抛到顶层导致浏览器白屏错误,在React16中我们可以实现这个方法来捕获子组件产生的错误,然后在父组件中妥善处理,比如搞个弹层通知用户网页崩溃等。

    在这个函数中请只进行错误恢复相关的处理,不要做其他流程控制方面的操作。比如:

总结

总结一下,以上讲的这些生命周期都有自己存在的意义,但在React使用过程中我们最常用到的生命周期函数是:

  constructor()
  
  componentDidMount()
  
  componentWillReceiveProps()
  
  componentWillUnmount()

当然需要用到其他生命周期函数可以按需正确使用。如果阅读文章过程中遇到问题欢迎评论进行修正! ^_^ ~