componentWillReceiveProps の廃止対応
React.js では、以下のライフサイクルメソッドが廃止されることがアナウンスされています。
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
この投稿では componentWillReceiveProps
の対応の仕方をまとめました。
なぜ廃止されるのか
blog.koba04.com の記事 に以下のようにあります。
React は v16 で React Fiber と呼ばれる実装に内部実装が書き換えられましたが、v16 系ではこれまでのバージョン
と互換性のある挙動をするようになっています(同期的)。 v17 では、ここの挙動がデフォルトで変更されます。その際、
Render Phase と Commit Phase のうち、Render Phase の方は非同期で処理され、さらに何度も呼ばれる可能性があ
ります。
Render Phase はインスタンスを作ったり差分を計算するいわゆる副作用のない部分です。Commit Phase は、Render
Phase の結果を元に実際に DOM などの Host 環境に適用する Phase で、同期的に処理されます。
そのため、Render Phase で呼ばれるライフサイクルメソッド(componentWillMount,
componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate)のうち
shouldComponentUpdate 以外は、安全性を保証できないため廃止されることになりました。
3つのアプローチ
componentWillReceiveProps の代案は以下の 3つです。
- componentDidUpdate に置き換える
- getDerivedStateFromProps に置き換える
- UNSAFE_componentWillReceiveProps に置き換える
componentWillReceiveProps で this.setState
して「いない」場合に 1、componentWillReceiveProps で this.setState
して「いる」場合に 2、といった順に検討しました。
1. componentDidUpdate に置き換える
マウントされたコンポーネントが新しい props を受け取る前のフックポイントが componentWillReceiveProps v16.2 です。新しい props は引数で取得できます。現在の props は this.props で取得できます。現在の state は this.state で取得できます。
componentDidUpdate は、コンポーネントが更新された直後のフックポイントで、引数で更新前の props と state などを確認できます。
componentDidUpdate への置き換えで要件を満たす場合は、次の 3つのルールを適用することで、機械的に作業をすすめられます。
- componentWillReceiveProps で利用できる this.props は、componentDidUpdate の第一引数に対応します。
- componentWillReceiveProps で利用できる this.state は、componentDidUpdate の第二引数に対応します。
- componentWillReceiveProps の引数から取得できた prop は、componentDidUpdate では this.props に対応します。
具体例です。
// 手順
// 1. this.props を prevProps へ置換して
// 2. this.state を prevState へ置換して
// 3. nextProps を this.props へ置換する
// before
componentWillReceiveProps(nextProps) {
const { foo, bar } = this.props
if (this.state.baz && nextProps.isLoading){ }
}
// after
componentDidUpdate(prevProps, prevState) {
const { foo, bar } = prevProps
if (prevState.baz && this.props.isLoading){ }
}
他に reactjs/reactjs.org の GitHub issue に対する Dan Abramov のコメント を挙げておきます。
2. getDerivedStateFromProps に置き換える
getDerivedStateFromProps と componentWillReceiveProps v16.2 のライフサイクル上の違いを「React.js の v16.3, v16.4 のライフサイクルメソッド」の用語に当てはめて列挙します。
- getDerivedStateFromProps も componentWillReceiveProps も Render phase である点は同一です。
- getDerivedStateFromProps は Mounting と Updating の両方で実行されるのに対し、 componentWillReceiveProps は Updating でのみ実行されます。
それから、
- Render Phase での安全性を保証するために、getDerivedStateFromProps は static メソッドで、componentWillReceiveProps はインスタンスメソッドであるという点は相違します。
この根拠として、blog.koba04.com に以下の記述をみつけました。
前述した通り、非同期レンダリングの世界では、更新処理の割り込みにより様々なタイミングで Render Phase が複数回
実行されます。そのため、React が管理する Props や State 以外のインスタンスが持つ状態を保証するのが難しくな
ります。そのため、この後紹介する componentWillReceiveProps の代わりと使われることが想定される
getDerivedStateFromProps は、static メソッドになっています。
getDerivedStateFromProps は引数で、新しい props と現在の state を取得できます。state を変更をするなら、変更箇所のオブジェクトを返却します。すると、それが既存のステートにマージされます。state を変更しないなら null を返却します。スタティックメソッドなので、コンポーネントのインスタンス変数にアクセスできません。それから、現在の props を確認できません。もともとの実装でそれらを利用している場合は、state として管理するように実装を変更する必要があります。
具体例として reactjs/rfcs > 0006-static-lifecycle-methods.md のコードを紹介します。
Before
class ExampleComponent extends React.Component {
state = {
derivedData: computeDerivedState(this.props)
};
componentWillReceiveProps(nextProps) {
if (this.props.someValue !== nextProps.someValue) {
this.setState({
derivedData: computeDerivedState(nextProps)
});
}
}
}
After
class ExampleComponent extends React.Component {
// Initialize state in constructor,
// Or with a property initializer.
state = {};
static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.someMirroredValue !== nextProps.someValue) {
return {
derivedData: computeDerivedState(nextProps),
someMirroredValue: nextProps.someValue
};
}
// Return null to indicate no change to state.
return null;
}
}
ただし、このアプローチはアンチパターンとなる可能性があります。公式ブログの Anti-pattern: Unconditionally copying props to state を参照して下さい。
3. UNSAFE_componentWillReceiveProps に置き換える
componentWillReceiveProps
を UNSAFE_componentWillReceiveProps
に置換することで v17.0 以降もこのライフサイクルメソッドを使うことができます。codemod の置換用 CLI コマンド があります。
このアプローチは、componentWillReceiveProps の廃止の論拠としてあげた安全性への不安に対する解決ではありません。
リファレンス
- 公式ブログ: Update on Async Rendering
- 公式ドキュメント: static getDerivedStateFromProps()
- 公式ドキュメント: componentDidUpdate
- 公式ドキュメント: componentWillReceiveProps v16.2
- 公式ドキュメント: State and Lifecycle v16.2
- 公式ブログ: Anti-pattern: Unconditionally copying props to state
- 公式ドキュメント: UNSAFE_componentWillReceiveProps
- reactjs/rfcs: 0006-static-lifecycle-methods
- React.js の v16.3, v16.4 のライフサイクルメソッド
- maesblog: React v16.3.0: 新しいLifecycleメソッドとcontext API【日本語翻訳】)
- koba04ブログ: React v16.3 changes