Re:(act)ゼロからはじめるHooks API
はじめに
こういう方はいませんか?
- Reactは書いたことあるけど結局Hooksってなんなの?
- 雰囲気でuseState, useEffectを使っている
- なんかわからんけど最近は関数コンポーネントとHooksが流行りなんだよね?
他にも
- そもそもHooksってなんなの?
- Reduxは関係ある?
- なんのためにあるの?
- 今まで通りClass Componentでよくない?
- 使わないとダメ?
本記事は上記のような人が「Hooks完全に理解した」というためのものです。
(「完全に理解した」というのは完全に理解することではない)
Reactの公式ドキュメントを読もう
Reactの公式ドキュメントは大変読みやすく、本記事の内容の多くはそこに書かれています。
Hooksとは
ざっくりいうと「関数コンポーネントでクラスコンポーネントのようないろいろな機能を使うためのReactのAPI」です。
わかるようで「なにがうれしいのか」「なにができるのか」がよくわからないですね。
掘り下げていきましょう。
関数コンポーネントとクラスコンポーネント
「関数コンポーネント」と「クラスコンポーネント」という言葉が出てきました。
これらについて考えてみましょう。
以下はそれぞれの簡単な実装例です。ざっと眺めて何か感じることはないでしょうか?
// 関数コンポーネント
const WelcomeButton: React.FC = (props) => {
const handleClick = () => {
console.log(`Hello, ${props.name}`);
};
return <button onClick={handleClick}>Hi</button>;
};
// クラスコンポーネント
class WelcomeButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(`Hello, ${this.props.name}`);
}
render() {
return <button onClick={this.handleClick}>Hi</button>;
}
}
関数コンポーネントのほうがスッキリしていませんか?
そうであれば関数コンポーネントで書きたいと思うのは自然な動機付けではないでしょうか。
関数コンポーネントの弊害
いざ関数コンポーネントで書き始めると起こることがあります。
- 書いてるうちにStateを持たせたくなった…
- 特定のライフサイクルで処理をしたい…
こういう場合、クラスコンポーネントに書き直さないといけないのでしょうか。
答えはイエスです。
では再度先程の例を見て、書き換えに必要な手順を上げてみましょう
// 関数コンポーネント
const WelcomeButton: React.FC = (props) => {
const handleClick = () => {
console.log(`Hello, ${props.name}`);
};
return <button onClick={handleClick}>Hi</button>;
};
// クラスコンポーネント
class WelcomeButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(`Hello, ${this.props.name}`);
}
render() {
return <button onClick={this.handleClick}>Hi</button>;
}
}
クラスコンポーネントに直すために必要な工程は以下です。
- classキーワードの追加
- constructorの追加
- renderの追加
しかし、本来やりたかったのは
- Stateを使いたい
これだけでしたね。
本質的でない変更が多いのは嬉しくありません。
ここで思い出すのは...
Hooksとは
関数コンポーネントでクラスコンポーネントのようないろいろな機能を使うためのReactのAPI
→ そうだHooksを使おう
つまり、Hooksでうれしいことは
関数コンポーネントのまま必要な機能だけを絞って追加できること
Hooksの種類
Hooksがなにかはわかりました。
Hooksにはどんなものがあるのか見ていきましょう。
Hooksは大きく分けて次の2種類あります。
- React公式が公開している10種のAPI
- カスタムhookとよばれる一定のルールに基づいたユーザー定義関数
ここで、後者はオフィシャルじゃないので一旦忘れるとしましょう。
つまり
10個わかればHooksを完全に理解できる
ということです。
しかも、10個の中にほぼ使わないものもあるので、覚えるべきことは更に減ります。
React公式が公開しているAPI
先述したAPI10種ですが、公式ドキュメントではこれらをさらに2つに分けています。
それを踏まえて10種を並べると次のとおりです。
- 基本のフック: 3種
- useState
- useEffect
- userContext
- 追加のフック: 7種
- useReducer
- useCallback
- useMemo
- useRef
- useImperativeHandle
- useLayoutEffect
- useDebugValue
Hooksを使ってみよう
Hooksを使う際の2つのルール
Hooks APIを使う場合に、守るべき2つのルールがあります。
- Reactの関数コンポーネントまたはカスタムhookから呼び出す
- →クラスコンポーネントでは使えない
- 関数のトップレベルに置く
これらはHooksに期待通りの動作をさせるために必要なものです。
ESLintのプラグインとしてeslint-plugin-react-hooks
という名前で公開されてるので、Hooksを用いるプロジェクトでは強制しましょう。
useStateの使い方
ここからはそれぞれの説明に移ります。
useStateは
- 状態を扱うhook
- 誤解を恐れずに言うとクラスコンポーネントのState
「useState
に初期値を渡すと、state変数と更新関数が返ってくる」というのが基本的な使い方です。
const Counter:FC = () => {
const [count, setCount] = useState(0); // 初期値0を渡している
return (
<div>
<button onClick={()=>setCount(count+1)} > // 更新関数setCountでcountを更新
count up
</button>
<p>{count}</p> // 初期値0のstate変数
</div>);
}
上記は状態が1つのケースでしたが、状態が複数ある場合も考えてみましょう。
クラスの場合はstateをオブジェクトとして宣言して、setStateで更新をマージする形になります。
constructor(props) {
super(props);
this.state = {
name: "taro",
age: 10
}
}
this.setState({
age: 30
})
ではHooksの場合はどうでしょうか?次のように書きます
const [name, setName] = useState("taro");
const [age, setAge] = useState(20);
setAge(30);
useStateを複数用いて、それぞれのstate変数・更新関数を定義します。
ここで起きているのは、関心を分離しているということです。
useState何が嬉しいの
useStateのメリットを整理すると次のようになります。
- 関数コンポーネントに状態をもたせることができる
- クラスコンポーネントに書き直さなくて良い
- 記述がシンプル
- 複数のuseState用いて関心を分割することができる
useEffectの使い方
次にuseEffectについて考えます。
useEffectは
- 副作用を扱うhook
です。
公式ドキュメントから引用すると、
Reactのライフサイクルに馴染みがある場合は、useEffectフックをcomponentDidMountとcomponentDidUpdateとcomponentWillUnmountがまとまったものだと考えることができます。
とあります。
useEffectの使い方としては以下です。
- useEffectの第1引数に副作用をもつ関数を渡す
- 副作用関数の戻り値に関数を設定することでcleanup処理を行える
- useEffectの第2引数に渡す配列が変更されない場合、useEffectは処理をスキップする
例を見てみましょう。
const User:FC = ({userId}) => {
const [userName, setUserName] = useState();
useEffect(async() => { // 副作用
const result = await axios(`https://example.com/username/${userId}`);
setUser(result.data);
}, [userId]);
return (
<div>
name: {userName}
</div>
);
}
useEffectの第1引数にfetch処理を書いています。[1]
また、第2引数にuseId
を指定することで、当該値が変更された場合にfetch処理を行っています。
useEffect何が嬉しいの
useEffectのメリットは以下です。
- 関数コンポーネントからfetchやタイマー処理などをの副作用を持つ処理を行うことができる
- ライフライクル単位ではなく、責務を単位にロジックを分割することができる
useContextの使い方
useContextは
- Contextを扱うhook
です。
コンテクストってなんだっけ?
公式ドキュメントから引用すると
コンテクストは各階層で手動でプロパティを下に渡すことなく、コンポーネントツリー内でデータを渡す方法を提供します。
Contextはprops drillingを避けるためのReactのAPIということがわかりました。
useContextの使用例を見てみましょう。
const ThemeContext = React.createContext(themes.light); // Contextの生成
function App() {
return (
<ThemeContext.Provider value={themes.dark}> // Providerの子孫コンポーネントではContextが伝播している
<Toolbar />
</ThemeContext.Provider>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext); // useContextにContextを渡すと値が取得できる
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
useContext何が嬉しいの
useContextのメリットをまとめます。
- コンポーネント間でグローバルな状態を共有することができる
- 認証情報、UIテーマなどが主な用例
時々使う追加のフックたち
追加のHooksについてはさらっと流します。
- useReducer
- reducerを定義してdispatchする局所的なreduxっぽいことができる
- useCallback
- 関数のメモ化によるパフォーマンスの最適化
- useMemo
- 処理結果のメモ化によるパフォーマンスの最適化
- useRef
- 子コンポーネントに命令したりDOMにアクセスしたり
ほぼor全く使わない追加のフック
こちらは「こういうのがあるんだな」程度で良いと思っています。
- useImperativeHandle
- 親コンポーネントから制御を受けるためのhook
- useLayoutEffect
- 同期的なuseEffect
- useDebugValue
- カスタムhookのデバッグに用いる
カスタムhook
カスタムHookは「Hooksの考え方といってのルールに従ったユーザー定義関数」です。
-
useXXX
という命名をする - Hooksを使用することができる
- 各種ライブラリもカスタムhookという形でHooks対応している
-
react-redux
のuseSelector
など
-
カスタムHook何が嬉しいの?
カスタムHookのメリットは以下です。
- コンポーネントからロジックの分離
- ロジックの再利用性
まとめ
完全に理解できましたか?
はじめに挙げた疑問に回答する形でおさらいしてみましょう。
- そもそもHooksってなんなの?
- 関数コンポーネントを便利に使うAPI
- Reduxは関係ある?
- 直接はないが、react-reduxはhooks対応している(useDispatchなど)
- なんのためにあるの?
- 関数コンポーネントを便利に使いたい
- ロジックを分離したい
- 今まで通りClass Componentでよくない?
- そちらが書きやすければどうぞ!
- 使わないとダメ?
- ダメじゃないよ!でも便利だよ
-
async関数を設定するとPromiseを返すためcleanup処理を設定できないことに留意 ↩︎
Discussion