React公式ドキュメントを読む12 コンポーネントにライフサイクルを導入する

前回からの続きです。

このページでは、ライフサイクルという概念が登場します。Reactコンポーネントでライフサイクルを利用すると何ができるようになるのでしょうのか。

1秒ごとに現在時刻が更新される「時刻表示プログラム」の実例を通して、ライフサイクルへの理解を深めます。

時刻表示プログラムを再利用しやすいコンポーネントにするには?(後編)

前回 は、「時刻表示プログラム」に改変を加えて、以下のプログラムにするところまで解説しました。

//React.Componentを継承してClockクラスを作成
class Clock extends React.Component {
  //this.state の初期状態を設定するクラスコンストラクタを追加する
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  //propsではなくstateを利用するように書き換える
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

//<Clock />要素からdateプロパティを削除する
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

//タイマーのコードは、後からClockコンポーネント内に追加する予定

上記のサンプルソースをHTMLファイルにしたものが以下です。

https://programming-world.net/sample/react_lifecycle1-1.html

ここまでの改変点は以下の通りです。

  • もともと関数コンポーネントであったものを、クラスコンポーネントにしてClockコンポーネントを作成しました。
  • Clockコンポーネントの内部では、時刻データをpropsとして受け取るのではなく、stateという内部の状態として管理するように変更しました。
  • これまで関数の呼び出し側で行っていたタイマーを削除しました。現状のプログラムでは現在時刻は表示しますが、1秒ごとに時刻表示が更新されません。

今回は、タイマーをClockコンポーネントに内包させます。これまでタイマーは関数の呼び出し側にありましたが、これをClockコンポーネントに内包させることで、自分自身だけで完結する時計コンポーネントに変更していきます。

1. Clockコンポーネントに「ライフサイクル」の概念を導入する

では、プログラムの改変を始めていきましょう。まず、Clockコンポーネント内に2つのメソッドを追加します。

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  //メソッドを追加 ※DOMとしてレンダリングされるタイミングで実行される
  componentDidMount() {

  }

  //メソッドを追加 ※DOMが削除されるタイミングで実行される
  componentWillUnmount() {

  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

上記サンプルに新しく追加したのは、 componentDidMount()、および、 componentWillUnmount()という2つのメソッドです。空のメソッドを追加しただけですから、この時点では何も機能していません。

Reactのコンポーネントには、マウント→更新→アンマウントという具合に状態が変化するサイクルがあります。これをコンポーネントの「ライフサイクル」と呼びます。

  • マウント(mounting) … コンポーネントがレンダリングされる用意が整った状態
  • 更新(updating) … コンポーネントがレンダリングされている状態
  • アンマウント(unmounting) … 現在のコンポーネントがクリアされた状態

このライフサイクルの概念を「時刻表示プログラム」の Clockコンポーネントに当てはめて考えてみましょう。まず、Clockコンポーネント内のタイマーが設定されたときがマウント (mounting) です。次に、Clockコンポーネントが生成したDOMがレンダリングされたときが更新(updating)です。そして、Clockコンポーネントが 生成したDOMが削除されたときがアンマウント(unmounting)です。

Reactコンポーネントにおけるライフサイクルの概念は、リソース管理の効率化に関係しています。多くのコンポーネントを有するアプリケーションでは、コンポーネントがクリアされた時点で、そのコンポーネントが占有していたリソースを開放することが重要になります。

それぞれのコンポーネントがどのライフサイクルにあるかを把握できれば、 コンポーネントが占有していたリソースを開放するタイミングを最適にできます。「時刻表示プログラム」の 例で言えば、タイマーを設定したときにマウント、タイマーをクリアしたときにアンマウントというライフサイクルになれば、リソースを開放するタイミングを最適にできます。

2. Clockコンポーネントがマウントした直後にタイマーを設定する

上記サンプルに新しく追加した componentDidMount() 、および、 componentWillUnmount() は、Reactでは特別な機能を持ったメソッド名です。これらは「ライフサイクルメソッド」と呼ばれ、それぞれ特定のタイミングで実行されることが決まっています。

  • componentDidMount() … コンポーネントがマウントした後(コンポーネントからの出力がDOMにレンダーされた後)に実行される
  • componentWillUnmount() … コンポーネントがアンマウントする前(コンポーネントが破棄される前)に実行される

componentDidMount()メソッドは、コンポーネントがマウントした直後にコードを実行します。コンポーネントがDOMとしてレンダーされた直後です。このタイミングでタイマーを設定したのが以下のコードです。

  //コンポーネントがマウントした直後にコードを実行
  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  //1秒ごとに呼び出されるtick()メソッド
  tick() {
    this.setState({
      date: new Date()  //現在時刻を更新
    });
  }

上記コードでは、tick()メソッドがタイマーで1秒ごとに呼び出されています。 tick()メソッドでは、現在時刻を setState() メソッドで state にセットしています。これはローカル state であり、Clockコンポーネント内だけで保持されるものです。

3. Clockコンポーネントがアンマウントする直前にタイマーをクリアする

componentWillUnmount()メソッドは、コンポーネントがアンマウントする直前にコードを実行します。このタイミングでタイマーをクリアしたのが以下のコードです。

  //コンポーネントがアンマウントする直前にコードを実行
  componentWillUnmount() {
    clearInterval(this.timerID);
  }

4. 改変後のコード全体

ここまでのコード改変で、Clockコンポーネントにタイマーを内包させることができました。以下に、コード全体を記載します。

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

上記のサンプルソースをHTMLファイルにしたものが以下です。

Clockコンポーネントは、自分自身でタイマーを設定して、自分自身でタイマーを破棄しています。しかも、 Clockコンポーネントにライフサイクルを導入することで、リソース確保と開放を最適のタイミングで行っています。

https://programming-world.net/sample/react_lifecycle1-2.html

前回から進めていた「時刻表示プログラム」をタイマーも含めて時刻表示に関する機能をすべて内包させて、自分自身だけで完結する時計コンポーネントに変更する一連のプログラム改変は完了しました。

次回は、完成したプログラムをあらためて検証します。