React Component ライフサイクル ひとめぐり (CodeSandbox 付き)

書籍『はじめてのフロントエンド開発』が2018年5月9日に技術評論社さまより刊行されております。

執筆プロジェクトでは React のパートを担当させていただいたのですが、執筆にあたり作成した React Component ライフサイクル に関する図 をこのたび CC0 ライセンスで公開しました。

React Component ライフサイクルに関する図

.ai および .svg フォーマットも取り揃えておりますのでどうぞご活用ください。

ところでお気づきの方もいらっしゃると思いますが、書籍執筆時点では React v16.3 のリリース前であったため、上記のダイアグラムも v16.3 で追加されたライフサイクルについての情報が盛り込まれていません。

更新版を作成する意思を見せつつ(言い訳)、今回は、React Component ライフサイクルのメソッドについて改めて整理しようという目的で筆を執りました。

Sand Box 的な何か

本エントリで登場するメソッドたちを利用したごく簡単なサンプルアプリを公開しています。

codesandbox.io

あらためて、React の Component ライフサイクルに関するメソッドについて

"React の Component ライフサイクルに関するメソッド" についての情報は公式ドキュメントにまとまっています。

reactjs.org

詳細は公式ドキュメントをご覧いただくとして、各メソッドについて簡単に整理します。

componentDidMount()

componentDidMount() は Component が Mount された後に実行されるメソッドです。用途の例は以下のとおりです。

  • Ajax を使ったデータフェッチを行う(初回)
  • DOM に対する処理を行う(初回)
  • タイマーをセットする
  • イベントリスナのセット

fetch() を利用してデータフェッチを行う例です。

fetchUser = (name: string) => {
  fetch(`https://api.github.com/users/${name}`)
    .then(res => res.json())
    .then(json => this.setState({ gitHubUser: json as GitHubUser }));
};

componentDidMount() {
  this.fetchUser(this.props.name);
}

SandBox では GitHubAPI を利用して指定したユーザーの Avatar を表示とタイマーのセットを行っています。

componentDidUpdate()

componentDidUpdate() は、Component の props または state が変更されたときに実行されます。用途の例は以下のとおりです。

  • Ajax を使ったデータフェッチを行う(二回目以降)
  • DOM に対する処理(二回目以降)
  • その他諸々

componentDidMount() は一度のみ実行されます。初回のデータフェッチは componentDidMount() に記述し、props ないし state の変更に応じて再フェッチする場合 componentDidUpdate() にも処理を書くことになります。

componentDidMount() {

  // 初回のフェッチ
  this.fetchUser(this.props.name);
}

componentDidUpdate(prevProps) {

  // props.id が変更されたら再フェッチ
  if (this.props.name !== prevProps.name) {
    this.fetchUser(this.props.name);
  }
}

SandBox では Select Box の 状態が変更された場合、GitHubAPI を再び呼び出す処理を記述しています。

componentDidMount() に無限ループするようなロジックを書いてしまいがちですので注意します。

// 無限にアップデートされ続ける
componentDidUpdate(prevProps: Props, prevState: State) {
  this.setState({ count: prevState.count++ });
}

componentWillUnmount()

componentWillUnmount() は Component が Unmount されるときに実行されます。componentDidMount() で行った処理の解除を行うことが多いでしょう。

  • タイマーを解除する
  • イベントリスナを解除する
  • 非同期処理を中止する
componentWillUnmount() {
  clearInterval(this.timerId);
}

getDerivedStateFromProps()

v16.3 で新設のメソッドです。戻り値とした Object の内容で state が更新されます。戻り値がない場合は state は更新されません。

static getDerivedStateFromProps(nextProps: Props, prevState: State) {
  const name = nextProps.name.toUpperCase();
  if (prevState.name !== name) {
    return { isDerivered: true, name };
  }
  return;
}

上記のサンプルはかなり苦肉の策感がありますが、実際、props の値をレンダリングに使うだけという場合、わざわざ state として管理する必要はありません。「初期値を props から求める + ユーザーの操作に応じて state の値が変更される」ような場合に利用します。

getDerivedStateFromProps() は static method のため、メソッド内で this.props.hoge !== nextProps.hoge のような比較処理は行えません。この点において componentWillReceiveProps() の代替ではない点に注意します。

shouldComponentUpdate()

shouldComponentUpdate() は、不要な再レンダリングを抑制してパフォーマンスの低下を防ぐ目的で利用されます。

shouldComponentUpdate(nextProps: Props, nextState: State) {

  // isVisible が変更されたときだけ再レンダリングを行う
  if (this.state.isVisible !== nextState.isVisible) {
      return true;
  }
  return false;
}

// shouldComponentUpdate() により props.text が更新されただけでは再レンダリングされない
render() {
  return <div>
    {this.state.isVisible && <p>{this.props.text}</p>}
  </div>
}

ただし、レンダリングに利用する値かどうかを shouldComponentUpdate() で判断してレンダリングのコントロールを行うことは適切ではありません。レンダリングに使用しない(が状態が変更される)ものは Class のメンバ変数として扱い state として管理しないようにします。

shouldComponentUpdate(nextProps: Props, nextState: State) {

  // (動作上は問題ないが) render() 内で利用しない状態をここで比較しているということは
  // そもそも state では無い説
  if (this.state.timerId !== nextState.timerId) {
      return false;
  }
  return true;
}

getSnapshotBeforeUpdate()

v16.3 で新設のメソッドです。getSnapshotBeforeUpdate() は、Component の再レンダリングが行われる直前に実行されます。戻り値がある場合、componentDidUpdate() の第三引数に値が渡ります。

getSnapshotBeforeUpdate() の用途としてスクロールポジションの管理に使う例が提示されています。

componentWillReceiveProps()

v1.7 で廃止予定。これまで componentWillReceiveProps() で行っていた処理は getDerivedStateFromProps() ないし componentDidUpdate() に移管できるはず。

componentWillUpdate()

v1.7 で廃止予定。同様に getDerivedStateFromProps() ないし componentDidUpdate() で代替できるはず。

componentWillMount()

v1.7 で廃止予定。使用しない。

書籍について

冒頭で言及した『はじめてのフロントエンド開発』は、パンダの表紙が目印の通称パンダ本です。

本エントリで触れた Component ライフサイクルを始め、React の "さわり" を眺めつつ、React を使った SPA を実装する流れです。TypeScript + React に挑戦してみたい方にもお勧めできます。

React、Angular、Vue.js、React Nativeを使って学ぶ はじめてのフロントエンド開発

React、Angular、Vue.js、React Nativeを使って学ぶ はじめてのフロントエンド開発

特徴としてはパンダが可愛いことです (語彙力)。

書籍で解説しているコードは下記の Repository で公開しています。あわせてご参照ください。

github.com

共著者のブログ

massa142.hatenablog.com

taisablog.com

以上です。