Reactの個人学習メモ - Hooksについて
- Reactを業務で触ることになったため、基本的な内容について学んだことのメモになります。
- 基本的なHooksについてまとめています。
context
- 書いた時のレベル: Reactチュートリアルを完了して、簡単なアプリケーションを2-3作成した程度
Hooksとは
- Reactが組み込みで用意している関数 (API)。
use~~
と書かれる。 - 独自に作成することもできる。その場合は、カスタムHooksと呼ぶことが多い。
- HooksはコンポーネントのトップレベルやカスタムHooks内でのみ使用できる。ループや条件文内で呼び出すことはできない。これは再レンダリングの際に毎回同じ数のHooksを呼んでいないとエラーになるためです。
- Reactはその昔、クラスコンポーネントが主体であったが、関数コンポーネントに移行する際、クラスコンポーネントができていた、インスタンス変数の取り扱いなどを関数で可能にするために生まれたもの?
代表的なHooks
ここでは、自分がこれまでよくみたHooksを取り上げ、その使い方をまとめてみました
stateフック
状態を管理するHooks
useState()
と useReducer()
の2つが代表例。
useState()
の使い方 1
import { useState } from 'react';
// 定義
const [count, setCount] = useState(0)
// 更新
<Button
onClick = {setCount((count) => count + 1}
/>
注意点
- setter関数で最新の状態を利用して値を更新するためには、関数を渡します。
- 例えば
setCount(count + 1)
と書くことも可能だが、同時実行時や setter関数内の処理に時間がかかる場合、複数回の処理が無視されるケースがあります。これは "count"が更新される前に2回目以降の呼び出しを行うと、更新前のcountを使って関数が処理を行うためです。
- 例えば
useState()
の使い方 2
import { useState } from 'react';
// 定義
const [user, setUser] = useState({
name: "",
age: "",
})
// 更新
<Button
onClick={() => setUser(user => ({...user, age: user.age + 1}))}
/>
注意点
-
useState()
は配列やオブジェクトも取り扱いします。 - その場合は、前の状態を基にして新しい状態を計算するようにします。
- state内のオブジェクトを直接変更しないように注意が必要です。
useReducer()
の使い方
import { useReducer } from 'react';
// reducer関数
function reducer(state, action) {
// ...
}
function MyComponent() {
// 定義
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
function handleClick() {
dispatch({ type: 'incremented_age' });
}
更新方法
- ユーザーのアクションを引数として、
dispatch
を呼び出します。 - Reactはそのアクションと現在のstateを
reducer()
に渡します。 -
reducer()
は渡されたアクションに合わせて、 state を更新しコンポーネントは新しいstateに合わせて再レンダリングされます。
useState()
と useReducer()
の使い分け
-
useState()
はよりシンプルに利用できるため、単純な boolean や 数値を保存するのに適しています。 -
useReducer()
は状態変更のロジックと状態を分けて書くため、より複雑な状態更新に向いています。例えば、条件に合わせた状態更新が必要な場合です。
Context フック
propsを渡すことなく、コンポーネント間で情報を共有できるようにする。
useContext()
が代表例。
useContext()
の使い方
import { useContext } from 'react';
// Form内のボタン
function Button() {
const theme = useContext(ThemeContext);
// ...
// コンテキストプロバイダによるラップ
function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
注意点
- contextを呼び出すには、呼び出し元のコンポーネントか親コンポーネントを、対応するコンテクストプロバイダでラップする必要があります。
-
useContext()
で呼び出すコンテキストは、呼び出すコンポーネントより上位、かつ最も近いものになります。そのため、useContext()
を呼び出すコンポーネント自体にプロバイダを設定しないようにします。 - コンテクストを更新したい場合、コンテクストの値をstateで管理し、stateの値をコンテクストとして渡します。
Effectフック
外部APIや3rd partyライブラリなどを接続するためのHooks
代表例は useEffect()
。
useEffect()
の使い方
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
注意点
-
useEffect()
は外部との接続のために使用します。もしuseEffect()
をたくさん書くような場合、カスタムHooksにしてしまう方が良い場合が多いです。 - 依存値のリストの書き方によって動作が異なります。
// 配列を渡す場合、a,b どちらかの値が更新された際に、再度実行される。
useEffect(() => {
// ...
}, [a, b]);
// 空の配列を渡す場合、初回のみ実行される。
useEffect(() => {
// ...
}, []);
// 何も渡さないと、コンポーネントのレンダーのたびに実行される。
// あまり使用することがないため、ミスを疑った方が良いです。
useEffect(() => {
// ...
});
パフォーマンス関連フック
キャッシュ済みの計算結果を再利用したり、データの変更がない場合の再レンダーをスキップしたりするためのHooks
useMemo()
, useCallback()
が代表例。
useMemo()
の使い方
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}
-
useMemo()
では、第1引数に、計算関数、第2引数に依存配列を渡します。 - 依存配列内の値が変更されると、計算関数を使って再計算を行います。
- それ以外の場合は、再レンダリングされた際にも計算をしないことで、不要な処理を避けることができます。
useCallback()
の使い方
import { useCallback } from 'react';
export default function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
-
useMemo()
が関数の結果をキャッシュすることに比べ、useCallback()
は関数自体をキャッシュします。Reactではレンダリングのたびに、関数も新しく作成されます。その作成をスキップし、特定の依存配列を指定するのが、useCallback()
の役割になります。
注意点
- メモ化が役立つのは、大規模な計算を行うときなど、明らかに時間がかかる処理があるときになります。
- 記述しても、みづらくなる以外のデメリットはそこまでないため、可能な限りメモ化することも可能ですが単純なページ表示などでは、
useMemo()
やuseCallback()
による最適化はほとんど効果がないため、効果的に使用するためには使いどころを見極める必要があります。