React公式ドキュメントを読む13 「時刻表示プログラム」を検証する

前々回前回にわたって、1秒ごとに現在時刻が更新される「時刻表示プログラム」 に、stateとライフサイクルを導入して改変してきました。

今回は、改変後のプログラムで何が起きているのか検証していきます。尚、このページはReact公式ドキュメントの内容を元にしています。元ページはこちらです。

改変後の「時刻表示プログラム」をプログラムを検証

以下のサンプルは、stateとライフサイクルを導入して改変した後の「時刻表示プログラム」です。このプログラムは、「1秒ごとに時刻を更新するClockコンポーネント」と、「Clockコンポーネントを画面にレンダリングするReactDOM.render()」 の2つで構成されています。

//1秒ごとに時刻を更新する
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>
    );
  }
}

//Clockコンポーネントを画面にレンダリングする
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

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

https://programming-world.net/sample/react_doc/clockcode1-1.html

このプログラムが実行される際に何が起きているのか、メソッドが呼び出される順番にそって確認していきましょう。

1. <Clock />が ReactDOM.render() に渡されると、まずClock コンポーネントのコンストラクタ が呼び出される

プログラムの実行は、 <Clock />が ReactDOM.render() に渡されるところから始まります。<Clock />が ReactDOM.render() に渡されると、React はClock コンポーネントのコンストラクタを呼び出します。

これは、Clockコンポーネントというクラスを呼び出した時点でインスタンスが作られるので、そのなかのコンストラクタが実行されるということです。コンストラクタが実行されると、this.stateに現在時刻が代入されて初期化されます。

  1. <Clock />が ReactDOM.render() に渡されます。
  2. すると、React はClock コンポーネントのコンストラクタを呼び出します。
  3. Clock は現在時刻を表示する必要があるので、現在時刻を含んだオブジェクトで this.state を初期化します。あとでこの state を更新していきます。

2. render() メソッドが呼び出される

Clock コンポーネントのコンストラクタが実行されたら、次に React は Clock コンポーネントの render() メソッドを呼び出します。

  1. 次に React は Clock コンポーネントの render() メソッドを呼び出します。
  2. これにより React は画面に何を表示すべきか知ります。
  3. そののちに、React は DOM を Clock のレンダー出力と一致するように更新します。

3. componentDidMount() ライフサイクルメソッドが呼び出される

Clock コンポーネントのrender() メソッドが実行されると、Clockコンポーネントで出力する要素が DOM に挿入されます。このタイミングでReact は componentDidMount()ライフサイクルメソッドを呼び出します。

呼び出されたcomponentDidMount()ライフサイクルメソッドは、tick()メソッドを1秒ごとに呼び出すようブラウザに要求します。

  1. Clock の出力が DOM に挿入されると、
  2. React は componentDidMount()ライフサイクルメソッドを呼び出します。
  3. その中で、tick()メソッドを1秒ごとに呼び出すようブラウザに要求します。

4. ブラウザは、tick()メソッドを1秒ごとに呼び出す

componentDidMount()ライフサイクルメソッドが実行されると、ブラウザがtick()メソッドを1秒ごとに呼び出すようになります。すると、1秒ごとにtick()メソッドが実行されて現在時刻が setState() によってstateが更新されます。

ここで、setState() を使ってstateを更新しているのがポイントです。 setState() を使ってstateを更新しているおかげで、Reactに state が変わったことが伝わるからです。state が変わったことを知ったReactは、それをきっかけに次のアクションへ移行できのです。

state が変わったことを知ったReactは、変化のあった箇所の表示を更新します。つまり、React は render()メソッドを再度呼び出して、変化のあった現在時刻の部分をレンダリングし直します。レンダリングし直すのは変化のあった現在時刻の部分だけで、他の「Hello, world!」や「It is」などの部分は書き換えません。

  1. ブラウザは、tick()メソッドを1秒ごとに呼び出します。
  2. tick()メソッドからは、現在時刻を含んだオブジェクトを引数として setState() を呼び出します。
  3. setState() が呼び出されると、React は state が変わったことが分かります。
  4. React は render()メソッドを再度呼び出して、画面上に何を表示すべきかを知ります。
  5. 前回とは render()メソッド内の this.state.date が異なっているので、今回レンダリングされる出力には新しく更新された時間が含まれています。
  6. それに従って React は DOM を更新します。

5. componentWillUnmount() ライフサイクルメソッドでタイマー停止

Clock コンポーネントが DOM から削除されることがあれば、このタイミングで React は componentWillUnmount()ライフサイクルメソッドを呼び出します。そして、componentWillUnmount()ライフサイクルメソッドが実行されると、ClearInterval()によってタイマーがクリアされます。

この componentWillUnmount()ライフサイクルメソッドを実行しておかないと、 Clock コンポーネント自体は削除されても、タイマーは実行され続けるのでリソースが開放されないことになってしまいます。

  1. Clock コンポーネントが DOM から削除されることがあれば、
  2. React は componentWillUnmount()ライフサイクルメソッドを呼び出します。
  3. componentWillUnmount()ライフサイクルメソッドが呼び出されると、タイマーが停止します。

最後に、すべてのコンポーネントが独立しているかどうか確認する

最後に、すべてのコンポーネントが独立しているかどうか確認するために、3つの<Clock />をレンダリングするAppコンポーネントを作成して動作検証してみます。

//1秒ごとに時刻を更新する
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>
    );
  }
}

//3つのClockコンポーネントを画面にレンダリングする
function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

//Appコンポーネントを画面にレンダリングする
ReactDOM.render(
  <App />,
  document.getElementById('root')
);

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

https://programming-world.net/sample/react_doc/clockcode1-2.html

3つのClockコンポーネントは、それぞれ独立してタイマーをセットして更新されています。

次回へ続きます。