Fork me on GitHub

(译)深入React的组件生命周期

本来想自己写一篇关于 React组件生命周期的博客做个总结,但看到这篇 ReactJs component lifecycle methods — A deep dive ,觉得它写的还是蛮清晰易懂,肯定比我写的好,所以就不再自己去搜肠刮肚了,直接翻译过来。。。

本篇博客适用于 React 16.3 及以下版本。

React 16.3 版本对组件的生命周期进行了大幅的改变,所以如果你在使用 16.3 之后的版本,可以查看这篇博客

React 和 它的用户界面

ReactJS 是一个用于构建用户界面的 JavaScript 库 ”,这是 React 的自我介绍。

什么是用户界面

用户通过在 UI 组件上的单击,悬停,按键或触发许多其他类型的事件来与应用程序交互。
所有UI组件都在浏览器中生成,并在某个时间点被删除。
整个界面由唯一的上帝管理——也就是用户。

用户界面是一个自由的游乐场,用户可以在其中做任何事情,而 React 就是用来帮助我们建造这个游乐场。

生命周期函数是什么,有什么用?

我们周围的所有东西都有生命周期——它们出生,成长,并在某个时刻死去,例如一棵树,任何的软件应用,你自己,一个 div 容器或者浏览器里的 UI 组件,都会经历出生,更新和成长,直到死去。

生命周期函数,就是在生命周期的不同阶段可以调用的各种函数。

假设我们在写一个 YouTube 应用,很明显我们的应用将会通过网络来缓冲视频,同时也使用电源里的电量(我们暂且假设我们的应用只做这两件事情)。

如果用户在开始播放视频后,切换到另一个应用程序,那么作为一个优秀的程序员,我们应该确保以最有效的方式使用网络和电池资源。

所以,每当用户切换到另一个应用程序时,我们应该停止/暂停视频的缓冲,从而停止使用网络和电池。

这就是 React 的生命周期函数能够做到的,通过生命周期函数,开发者可以决定在 UI 界面特定的初始化,更新和销毁的时刻做些什么事情,从而开发出优质的应用。

深入理解组件的生命周期可以帮助你开发出更好的 React 用户界面。

React 组件的四个阶段

React 组件像世上的其他东西一样,有以下几个生命阶段:

  • 初始化 Initialization
  • 挂载 Mounting
  • 更新 Update
  • 销毁 Unmounting

下面这张图片直观的展示了 React 组件的各个生命阶段和生命周期函数:

1_sn-ftowp0_VVRbeUAFECMA.png

为了更直观的解释生命周期钩子函数,我们将创建一个叫 Contra 的音乐播放器 React 应用。

让我们开始吧。

(一)初始化 Initialization

在这个阶段,React 设置组件的初始statedefaultProps ,从而为组件即将到来的艰辛历程作准备。

Contra 音乐播放器在开始时是下面这样:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
class ContraMusicPlayer extends React.Component {
static defaultProps = {
theme: 'dark'
}

constructor(props) {
super(props);
this.state = {
volume: 70,
status: 'pause'
}
}
}

组件在构造函数中初始化 state,随后你可以使用 setState 来改变它。

defaultProps 类静态属性定义了组件的所有 props 的默认值,可以被外部传入的 props 值覆盖。

当使用类似 <ContraMusicPlayer /> 来渲染 Contra 音乐播放器时,它将以 70% 的音量,暂停状态和 drak 暗色主题开始。

当使用类似 <ContraMusicPlayer theme="light" /> 来渲染 Contra 音乐播放器时,它将以 70% 的音量,暂停状态和 light 亮色主题开始。

(二)挂载 Mounting

在准备好初始所需的 stateprops之后,我们的 React 组件已经准备好挂载到浏览器的 DOM 树中了。

这个阶段提供了挂载前和挂载后的钩子方法。在这个阶段有如下的方法会被调用:

  • componentWillMountReact 组件将要挂载到 DOM 中时执行,也就是说,在这个方法之后,组件将会被挂载。所有你想在组件挂载之前做的事情,都应该定义在这个函数中。

    这个函数在整个生命周期中只会在初次渲染之前被调用一次。

    提示: componentWillMount 经常被用来初始化 statesprops,因此目前有比较大的争议它是否应该合并到组件类的构造函数中。

  • render 挂载组件到浏览器中。它是个纯函数,也就是说,给它相同的输入,它总是返回相同的输出。

    我们的音乐播放器应用的 render 方法大概像下面这样:

    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    render() {
    <div>
    <PlayHeader>
    <Status/>
    <VolumeBar/>
    <SeekBar/>
    </PlayHeader>
    </div>
    }
  • componentDidMount 是在组件已挂载到 DOM 后会被调用的钩子函数。

    这个函数在整个生命周期中只会在初次渲染之后被调用一次。

    在这个函数中,我们已经可以接触到 DOM, 因此我们可以在这个函数中初始化一些需要操作 DOMJS 库,例如 D3 或者 JQuery

    示例: 在我们的音乐播放器中,我们想绘制歌曲的波形,在这个方法中我们可以继承 D3 或者其他第三方 JS 库。

    下面是一个在此钩子函数中引入和初始化 highcharts的例子:

    JavaScript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    componentDidMount() {
    if (this.props.modules) {
    this.props.modules.forEach(function (module) {
    module(Highcharts);
    });
    }
    // Set container which the chart should render to.
    this.chart = new Highcharts[this.props.type || "Chart"](
    this.props.container,
    this.props.options
    );
    }

    我们应该在哪里进行 API 调用?

    API 调用应该在 componentDidMount 方法中, 可以参考这篇文章来了解更多关于如何进行 API 调用的相关内容。

(三)更新 Update

这个阶段在 react 组件已经处于浏览器中并开始响应和接受新值进行更新的时候。

组件有两种被更新的方式,发送新的 props 和更新 state

让我们看看在调用setState 更新当前 state时会触发哪些钩子函数:

  • shouldComponentUpdate 告诉 React 当组件接受到新的 props 或者 state 被更新时,React 是应该重新渲染组件或者跳过渲染 ?

    此函数提出了一个问题, 组件应该被更新吗

    因此此函数应该返回一个布尔值true或者false,来指明组件应该被重新渲染还是跳过。

    默认的,此生命周期函数返回 true

    示例: 下面是一个只有当 propsstatus属性变化时才重新渲染组件的小例子:

    JavaScript
    1
    2
    3
    4
    shouldComponentUpdate(nextProps, nextState) {
    let shouldUpdate = this.props.status !== nextProps.status;
    return shouldUpdate;
    }

    这个方法通常用于当渲染函数 render 是一个很重的方法时,你需要在某些时刻它不被自动调用。

    举个例子,比如在组件的一次渲染中,会生成上千个素数,我们就可以通过这个方法,来控制只在我们需要时进行组件的 render

  • componentWillUpdateshouldComponentUpdate 返回 true 时被调用。此方法仅用来为即将进行的 render 做准备,类似于 componentWillMount 或者 constructor

    如果在渲染某些项目和数据之前,需要进行一些运算,这个函数中是做这些的合适位置。

  • render 组件被渲染

  • componentDidUpdate 在更新后的组件被渲染到 DOM 之后调用。

    这个函数通常用于确定和触发第三方库的更新和重载。

下面这个列表,是当父组件传入了新的 props 时子组件中将会被调用的生命周期函数:

  • componentWillReceivePropsprops 已经改变同时不是首次 render 时被调用。

  • 在某些时候 state 依赖于 props,因此当 porps 改变时,state 也应该被同步改变。这个方法就是同步 propsstate 逻辑的位置。

    state不存在类似的方法,因为 props只在组件内部可读,永远不会依赖于组件的 state

示例: 下面是一个当 props 变化时 state 保持同步的例子:

JavaScript
1
2
3
4
5
6
7
componentWillReceiveProps(nextProps) {
if (this.props.status !== nextProps.status) {
this.setState({
status: nextProps.status
});
}
}

随后的生命周期钩子调用,和我们在上面提到当setState时的生命周期函数调用完全一致:

  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

(四)销毁 Unmounting

在这个阶段,组件不再被需要,即将在 DOM 中被销毁。在此阶段以下方法将会被调用:

  • componentWillUnmount 此函数是生命周期函数的最后一个。

    它在组件即将被从 DOM 中移除前被调用.

    示例: 在这个函数中,我们可以进行在组件被销毁之前执行相应的清理工作。例如退出登录,清除用户数据和认证token等。

    JavaScript
    1
    2
    3
    4
    5
    componentWillUnmount() {
    this.chart.destroy();
    this.resetLocalStorage();
    this.clearSession();
    }
----本文结束感谢阅读----