React 16.3で<React.StrictMode>というコンポーネントが登場しました。大半のチェック点はうなずけるものだったのですが、1つ気になることがありました。
<React.StrictMode>とは
JavaScriptをやっている方なら"use strict";によるJavaScript自体のStrictモードはご存知かと思いますが、<React.StrictMode>もそれと同様、今までの書き方で問題が出る部分をあぶり出すためのモードです。なお、React本体のproductionビルドではチェックが無効となりますので、developmentビルドを使う必要があります。
背景
React 17からレンダリングを非同期化するという話が進んでいて、それで問題になるコードをチェックするために<React.StrictMode>が導入された、ということのようです。
deprecatedな機能のチェック
旧式になった機能を使っていると、その旨がコンソールに出力されます。
- 古い\ライフサイクル(
componentWillMount、componentWillUpdate、componentWillReceiveProps) - 文字列による
ref指定 - 旧形式のコンテキスト
findDOMNode
とはいえ、このあたりはすでにdeprecatedの宣言もあり移行手順も公開されているので、(依存しているコンポーネントが変更できないなどの状況なら別として)そう大問題にはならないかと思います。
非同期レンダリングのフェーズ
では、ここからが本題です。以前に別な記事で書きましたが、Reactの非同期レンダリングにおいては、状態が「Render Phase」と「Commit Phase」の2つに大きく分けられます。
そして、getSnapshotBeforeUpdate、componentDidMount、componentDidUpdate、componentWillUnmountはCommit Phaseのコールバックですが、それ以外のrender、コンストラクタ、setStateのupdater、getDerivedStateFromProps、shouldComponentUpdateなどはRender Phaseのコールバックです。
そして、非同期レンダリングを行う上では、Commit Phaseのコールバックは実行されるべき1回のレンダリングに対応して確実に1回実行されますが、Render Phaseのコールバックは複数回実行されうるものとなります。そこで、<React.StrictMode>ではそういった問題点の洗い出しのため、render、コンストラクタ、setStateのupdater、getDerivedStateFromPropsを1回のrenderに対して2回実行するようになります。
複数回のコンストラクタ…?
通常、updaterやrenderで副作用のあることを行おうという人はいないかと思いますが1、コンストラクタは他にも影響することを行うので、気になる例もあるかと思います。
実際にCodePenで動かして調べてみると、以下のような感じでした。
- 確かにコンストラクタは2回実行される
- もう1個生成されたはずのインスタンスは、
componentWillUnmountも何も起こさず消滅する(あとからsetStateしようとするとエラーに)
ということで、コンストラクタでやることは「インスタンス変数の設定」、そして「外部に対しては冪等になる仕事」ということになります。
- 外部に対して破壊的に働くコードについては、
componentDidMountに移動させるのが適当でしょう。 - CodePenでやったような、「あとから値を
setStateする」ような処理も、componentDidMountから動かす(そして、componentWillUnmountした際には正しく中断できるようにする)ことが必要と考えます。
-
もっとも、自分は過去に
render()内でsetState()しようとしたことがあります。 ↩