React コンポーネントのライフサイクルとメソッドの役割について

公式ドキュメント [React.Component](https://reactjs.org/docs/react-component.html) を読んだメモです。

# コンポーネントのライフサイクルと、実行されるメソッド

[React Lifecycle Methods Diagram](http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/) より。
<img width="566" alt="20181122145036.png" src="https://qiita-image-store.s3.amazonaws.com/0/2596/7cda7f85-5e91-82b3-3230-c3cdd0b736d2.png">

**太字**が主要なライフサイクルメソッド。`constructor()`、`render()`、`componentDidMount()`、`componentDidUpdate()`、`componentWillUnmount()`。

# 前提

`props` は、コンポーネントの外から来る値。親コンポーネントから受け継ぐ。変更不可。
`state` はコンポーネント内のプライベートな値。変更可。この `state` は Redux の state とは別物。コンポーネントの状態。
両方の値がレンダリング結果に影響する。

 * https://preact.gitbooks.io/react-book/content/jsx/index.html

# 主要なライフサイクルメソッドの役割

## constructor(props)

2つの用途にのみ使う。

 * `this.state` の初期化
 * イベントハンドラの `bind`

```javascript
constructor(props) {
  super(props);
  // ここで this.setState() を使ってはいけない
  this.state = { counter: 0 };
  this.handleClick = this.handleClick.bind(this);
}
```

 * `props` を `this.state` にコピーしてはダメ
 * `this.state` に直接代入していいのは、コンストラクタのみ。他は `this.setState()` を使う
 * コンストラクタで副作用を発生させないこと。そういう時は `componentDidMount()` を使う

## render()

コンポーネントで唯一の必須メソッド。

`this.props` と `this.state` を読んで、以下のどれかを返す。

 * **React elements** - JSX などで作る
 * **Arrays and fragments** - [複数の elements を返す](https://reactjs.org/docs/fragments.html)
 * **Portals** - [別の DOM ノードにレンダリングする](https://reactjs.org/docs/portals.html)
 * **String and numbers** - テキストとしてレンダリングする
 * **Booleans or null** - なにもレンダリングしない。`return test && <Child />` の `test` が `false` の場合、`Boolean` が返って何もレンダリングしない

`render()` は pure な function であるべき。

 * コンポーネントの state を変えない
 * 同じ入力に対して毎回同じ結果を返す
 * ブラウザと直接インタラクトしない。インタラクトするには `componentDidMount()` など他のメソッドを使う

`shouldComponentUpdate()` が `false` を返したら実行されない。

## componentDidMount()

 * コンポーネントがツリーにマウントされた時に実行される
 * DOM ノードが必要になるような初期化処理はここでやる
 * ネットワークへのリクエストをここでやる
 * subscription をここでやって、`componentWillUnmount()` で unsubscribe する

`componentDidMount()` で `setState()` を実行すると追加のレンダリングが実行される。この時、ブラウザが画面を連続して描画したりはしない。2回 `render()` が実行されても、途中の画面の変化をユーザが目にすることはない。

画面が変わらなくても、パフォーマンスには影響するので注意。

## componentDidUpdate(prevProps, prevState, snapshot)

 * 初回は実行されない。更新時に実行される
 * コンポーネントが更新されたタイミングで DOM を操作したいときは、ここでやる
 * 更新前後の props を比較して、ネットワークにリクエストしたいときは、ここでやる
 * `setState()` を実行するときは、条件を設定する。条件無しだとループする
   * レンダリング → `setState()` → レンダリング → `setState()` → …
 * `componentDidMount()` と同じく、再レンダリングの過程は画面に描画されないけれども、パフォーマンスには影響し得る

## componentWillUnmount()

 * コンポーネントがアンマウントされるときに実行される
 * クリーンアップの処理をここでやる
   * タイマーの無効化
   * ネットワークリクエストのキャンセル
   * unsubscribe
 * もうレンダリングされないので、`setState()` を呼んではいけない

# レアなライフサイクルメソッド

## shouldComponentUpdate(nextProps, nextState)

パフォーマンスの目的でのみ使用する。

今のところ、false を返すとレンダリングが実行されないが、将来は、場合によってレンダリングするように変更される可能性がある。このメソッドの結果が、厳密な制御ではなくヒントとして使われるようになる可能性がある。

## getDerivedStateFromProps(props, state)

使わない。

## getSnapshotBeforeUpdate(prevProps, prevState)

DOM が変化する前の値をとっておいて、`componentDidUpdate(prevProps, prevState, snapshot)` の3番目の引数で受け取って使う。


# Redux と一緒に使う場合

Redux と一緒に使う場合、ライフサイクルメソッドの `componentDidMount`、`componentDidUpdate`、`componentWillUnmount` から、アクションを `dispatch` できる。

`render` → `componentDidMount` → (Redux の) state 変更 → `render` のように 2回レンダリングすることになるが、受け入れる。2回 `render()` しても、画面の書き換えは1回だけ。