2018-04-01のJS: TypeScript 2.8、React 16.3.0、TensorFlow.js - JSer.info を読んで。
React 16.3.0 から StrictMode
コンポーネントというものが追加されたらしい。
公式ドキュメントを読んでみます。
StrictMode
とは
StrictMode
はアプリの潜在的な問題を検出するために追加されたコンポーネント。コンポーネントだが Fragment
などと同じく UI として画面に表示されるものはない。
<StrictMode>...</StrictMode>
で囲まれた子孫コンポーネントに対し、いくつかのチェックを行う。
また development モードでのみ動作し、 production build 時には影響を与えない。
ScrictMode
がチェックしてくれること
今のところ以下。今後のリリースで機能は追加予定とのこと(Additional functionality will be added with future releases of React.)
- 安全でないライフサイクルメソッドの使用(Identifying components with unsafe lifecycles)
- レガシーな string ref の使用(Warning about legacy string ref API usage)
- 予期せぬ副作用の検出(Detecting unexpected side effects)
- (検出するために、特定のライフサイクルメソッドを二度実行する)
1. 安全でないライフサイクルメソッドの使用
背景として、v16.3.0 以降は非同期レンダリングなどのサポートのために一部のライフサイクルメソッド
componentWillMount
componentWillReceiveProps
componentWillUpdate
が今後削除予定となった。
参考:Update on Async Rendering - React Blog
(こっちはまだ読んでない)
<StrictMode>
の子孫コンポーネントでこれらのライフサイクルメソッドを使用しているものがあれば、ブラウザのコンソールで warning が出力される。
(サンプル)
import React, { Component, StrictMode } from 'react'; class UnsafeComponent extends Component { componentWillMount() { console.log('componentWillMount'); } componentWillReceiveProps(props) { console.log('componentWillReceiveProps'); } render() { return <div>Unsafe Component</div>; } } export default function App() { return ( <StrictMode> <UnsafeComponent /> </StrictMode> ); }
(結果)
Warning: Unsafe lifecycle methods were found within a strict-mode tree: in App in AppContainer componentWillMount: Please update the following components to use componentDidMount instead: UnsafeComponent componentWillReceiveProps: Please update the following components to use static getDerivedStateFromProps instead: UnsafeComponent Learn more about this warning here: https://fb.me/react-strict-mode-warnings
該当のコンポーネント名と使っているライフサイクルメソッドが表示されている。
2. レガシーな string ref の使用
ref
を使ってコンポーネントを参照するための方法はこれまで2通りあって
ref="input"
のように文字列で指定するref={(element) => this.input = element}
のように callback 関数で指定する
このうち 1 の文字列で指定する方にはいくつか問題があったらしく、ドキュメントでも 2 の方法を推奨していた。
<StrictMode>
の子孫コンポーネントで 1 の string ref を使っている箇所があると、こちらも同様にブラウザのコンソールでwarning が出る。
(サンプル)
import React, { Component, StrictMode } from 'react'; class LegacyRef extends Component { handleClick = () => { const name = this.refs.name.value; console.log('LegacyRef', name); }; render() { return ( <div> <input ref="name" /> <button onClick={this.handleClick}>click me</button> </div> ); } } class NewRef extends Component { constructor(props) { super(props); this.nameRef = React.createRef(); } handleClick = () => { const name = this.nameRef.current.value; console.log('NewRef', name); }; render() { return ( <div> <input ref={this.nameRef} /> <button onClick={this.handleClick}>click me</button> </div> ); } } export default function App() { return ( <StrictMode> <LegacyRef /> <NewRef /> </StrictMode> ); }
(結果)
Warning: A string ref, "name", has been found within a strict mode tree. String refs are a source of potential bugs and should be avoided. We recommend using createRef() instead. in div (created by LegacyRef) in LegacyRef (created by App) in App in AppContainer Learn more about using refs safely here: https://fb.me/react-strict-mode-string-ref
余談: createRef()
を使った新しい ref の方法
上のサンプルで、 NewRef
コンポーネントがやっているのは v16.3.0 から追加された新しい ref の実装方法で、 createRef()
という関数を使う。
従来の string ref のような書き方で、かつ string ref のときの問題点は解決されている。らしい。
参考:React v16.3.0: New lifecycles and context API - React Blog の createRef API の項
なお createRef()
の導入後も 2 の callback を使った方法はサポートされるので、置き換える必要はない。
3. 予期せぬ副作用の検出
背景として、React の動作時には大きく2つのフェーズがある。
- render フェーズ:DOM に適用する必要のある変更を決定する。
render
メソッドが呼ばれ、結果を直前のrender
の結果と比較する - commit フェーズ:React がすべての変更を DOM に適用する。
componentDidMount
やcomponentDidUpdate
などのライフサイクルメソッドもこのフェーズで呼ばれる
一般に commit フェーズは速いが render は遅い。そのため、非同期レンダリングによってレンダリング処理を複数の小さな処理に分割し、ブラウザをブロックしないように停止と再開をしながらレンダリングを行う。
これにより、commit 前に render フェーズのライフサイクルメソッドが複数回実行される可能性が生じる。
(このあたりはよくわかっていない)
ので、これらのライフサイクルメソッドに副作用がないことが重要となる。
これらのライフサイクルメソッドとは具体的には以下。
- Class コンポーネントの
constructor
メソッド render
setState
の第一引数に関数を渡したときの関数(updater と呼ぶらしい)getDerivedStateFromProps
(v16.3.0 からcomponentWillReceiveProps
の代替として追加)
ただ、これらのメソッドに副作用がないことを自動的に検出することは難しいため、StrictMode
ではこれらのメソッドを2回ずつ実行する。
(サンプル)
class SideEffect extends Component { constructor(props) { super(props); this.state = { count: 0 }; console.log('SideEffect constructor'); } increment = () => { this.setState((prevState, props) => { console.log('SideEffect updater', prevState); return { count: prevState.count + 1, }; }); }; render() { console.log('SideEffect render'); return <div onClick={this.increment}>{this.state.count}</div>; } } export default function App() { return ( <StrictMode> <SideEffect /> </StrictMode> ); }
(結果)
コード
一応上げておく。