React 16.8の新機能フック (hook)を使えば、状態を持つようなクラスもStateless Functional Components(SFC)で表現できるようになりました。
これからはどんどんSFCで書いていきましょう。
mizumotok.hatenablog.jp
- ステート
- componentDidMount / componentDidUpdate
- componentWillUnmount
- componentWillReceiveProps
- shouldComponentUpdate
- DOM Refs
- インスタンス変数
- まとめ
ステート
クラスを使いたくなるのはほとんどがステートを使いたいからです。
SFCで書くにはuseStateフックを使います。
const [state, setState] = useState(initialState);
クラス
class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } }
SFC
import React, { useState } from 'react'; function Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
componentDidMount / componentDidUpdate
componentDidMount と componentDidUpdateは同一の処理を書くことが多かったのですが、useEffectフックで一つにまとまりました。
useEffectはいくつも書くことができますので、今までは処理(副作用)がいくつあっても一つのライフサイクル関数に書いていましたが、副作用ごとに分けて書くことができます。
useEffect(didUpdate);
クラス
class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } componentDidMount() { document.title = `You clicked ${this.state.count} times`; } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } }
SFC
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
componentDidMountのみ
componentDidUpdateは必要ないときもあります。
その場合は第2引数に空配列[]を入れればいいです。
useEffect(() => { console.log('mounted!'); }, []);
componentWillUnmount
React Hooksではクリーンアップ関数と呼ばれます。
useEffectの第一引数の関数の戻り値がクリーンアップ関数になります。
副作用ごとに分けて書くことができるおかげで、副作用ごとにクリーンアップ関数を定義することになり、コードの見通しがよくなります。
クラス
componentDidMount() { this.subscription = props.source.subscribe(); } componentDidUpdate() { this.subscription = props.source.subscribe(); } componentWillUnmount() { this.subscription.unsubscribe(); }
SFC
useEffect(() => { const subscription = props.source.subscribe(); return () => { // Clean up the subscription subscription.unsubscribe(); }; });
componentWillReceiveProps
変更前の値が不要
変更前の値が不要であれば、useEffectを使えます。
componentWillReceivePropsは監視対象の変数が変更したかどうかを確認する必要がありましたが、useEffectでは第2引数に指定した変数が変更したときのみ動くので、その確認する処理が不要になります。
クラス
componentWillReceiveProps(nextProps) { if (nextProps.count !== this.props.count) { console.log(`next count = ${nextProps.count}`); } }
SFC
useEffect(() => { console.log(`next count = ${props.count}`); }, [props.count])
変更前の値が必要
変更前の値はインスタンス変数に入れておく必要がありましたが、useRefフックでインスタンス変数を代替できます。
const refContainer = useRef(initialValue);
ステートの変更前の値をとっておきたい場合にも応用できます。
クラス
import React; class Example extends React.Component { constructor(props) { super(props); this.prevCount = 0; } componentWillReceiveProps(nextProps) { if (nextProps.count !== this.props.count) { this.prevCount = this.props.count; } render() { return ( <div> <div>prev Count: {this.prevCount}</div> <div>current Count: {this.props.count}</div> </div> ); } }
SFC
import React, { useEffect, useRef } from 'react'; function Example(props) { const prevCountRef = useRef(); useEffect(() => { prevCountRef.current = props.count; }, [props.count]); const prevCount = prevCountRef.current; return ( <div> <div>prev Count: {prevCount}</div> <div>current Count: {props.count}</div> </div> ); }
shouldComponentUpdate
これはフックでは対応できません。React.memoを使って、コンポーネントまるごとメモ化しておく必要があります。
クラス
shouldComponentUpdate(nextProps) { return nextProps.count !== this.props.count }
SFC
function _MyComponent(props) { ... } const MyComponent = React.memo( _MyComponent, (prevProps, nextProps) => nextProps.count !== prevProps.count );
DOM Refs
componentWillReceivePropsのときにも使用したuseRefフックが使えます。
const refContainer = useRef(initialValue);
クラス
import React from 'react'; class Example extends React.Component { constructor(props) { super(props); this.inputRef = React.createRef(); } componentDidMount() { this.inputRef.current.focus(); } render() { return <input type="text" ref={this.inputRef} />; } }
SFC
import React, { useEffect, useRef } from 'react'; function Example(props) { const inputRef = useRef(); useEffect(() => { if (inputRef.current) inputRef.current.focus(); } }); return <input type="text" ref={inputRef} />; }
インスタンス変数
this.myVar のようなインスタンスがメンバとして持っている変数ですが、これもuseRefフックで代替します。
useRefフックがインスタンス変数の代替で、DOM refsやcomponentWillReceivePropsに応用できるという方が正確でしょうか。
まとめ
React Hooksを使えば、ステートやライフサイクルメソッド、インスタンス変数といったクラスを使わなければならない理由だった要素を代替できます。
ほとんどのケースでクラスは不要になります。
React Hooksにはその他の機能や利点もあります。例えばuseContextフックではContextの値を取得できるので、Context.Consumerが不要になります。useEffectを応用してカスタムフックを作れば、HOC(High Order Components)の代替えにもなります。その場合、仮想DOMの階層を減らせるので開発中のDOMの確認が楽になります。
mizumotok.hatenablog.jp
- 作者: 吉田裕美
- 出版社/メーカー: 秀和システム
- 発売日: 2017/09/16
- メディア: 単行本
- この商品を含むブログ (1件) を見る