React Hooks

React18 version

React Hooksとは

React16.8~から追加された機能。

関数コンポーネントでも状態管理できるのため

  • クラスコンポーネントより記述が簡潔、読みやすい
  • 再利用性が高くなる
  • テストコードが書きやすい

よく使うHooks

  • useState
  • useEffect
  • useContext
  • useRef
  • useReducer
  • useMemo & useCallback
  • custom hooks

useState

1
2
3
4
5
6
7
function App() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>+</button>
<p>{count}</p>
);
}

なぜ直接に変数&関数を使わないか:

  • useState を利用するこそ、React の仮想 DOM & diff の仕組みを使えて、ブラウザの再レンダリングを実行させる(普通の変数と関数なら、値が更新してもブラウザの再レンダリングが実行させない)

useEffect

エフェクトを使うことで、コンポーネントを外部システムに接続し、同期させることができます。これには、ネットワーク、ブラウザの DOM、アニメーション、別の UI ライブラリを使って書かれたウィジェット、その他の非 React コードの処理が含まれます。

1
2
3
4
5
6
7
function App() {
useEffect(() => {
console.log("hello");
// setCount(count + 1); useEffect内ではセッターは呼ばない -> 無限ループになる
// return () => alert("goodbye"); //unmount時に実行される
}, [count]);
}

無限ループの問題

第二引数は依存値のリスト(発火するタイミングを決める)

  • Object.isを使って依存値を以前の値と比較する
  • 第二引数を省略すると、コンポーネントの毎回のレンダー後(mountedとき)にEffectが再実行される
  • リアクティブな依存値の配列を渡す例

Strict Mode + Development Modeの場合、2回実行する

クライアント上でのみ実行、サーバレンダリング中には実行されない

useContext

Redux とは似てる、グローバルでデータを共有し、より簡潔に prop drilling 問題を解決できる

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Context = createContext()

const Mago = () => {
// データを使う:useContext
const { money } = useContext(Context) // 親→孫へ直接データの受け渡しができました
return <p>{money}円</p>
}
const Kodomo = () => <Mago />
const Oya = () => {
return (
// データを共有:Provider value={}
<Context.Provider value={{ money: 10000 }}>
<Kodomo />
</Context.Provider>
)
}

useRef

指定した値の情報を参照することができる

1
2
3
4
5
6
7
8
9
10
11
12
function App() {
const ref = useRef();
const handleRef = () => {
console.log(ref.current.value);
ref.current.focus();
console.log(ref.current.offsetHeight);
};
return (
<input type="text" ref={ref} />
<button onClick={handleRef}>UseRef</button>
);
}

useReducer

Redux Store:

  • State
  • Dispatch(action)
  • Reducer(state, action) => 次のstate (stateをどのように更新するかを指定する)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
default:
return state;
}
};

function App() {
const [state, dispatch] = useReducer(reducer, 0);
return (
<p>カウント: {state}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
);
}

useMemo & useCallback

第二引数に依存配列を取って、

  • useCallback:関数のメモ化(メモ化したコールバック関数そのものを返す)
  • useMemo:変数のメモ化(関数の結果をメモして返す)

何をメモすべきか:

  • useMemo 使用法
  • useCallback 使用法

実際には、以下のいくつかの原則に従うことで、多くのメモ化を不要にすることができます。

コンポーネントが他のコンポーネントを視覚的にラップするときは、それが子として JSX を受け入れるようにします。これにより、ラッパコンポーネントが自身の state を更新しても、React はその子を再レンダーする必要がないことを認識します。

ローカル state を優先し、必要以上に state のリフトアップを行わないようにします。フォームや、アイテムがホバーされているかどうか、といった頻繁に変化する state は、ツリーのトップやグローバルの状態ライブラリに保持しないでください。

レンダーロジックを純粋に保ちます。コンポーネントの再レンダーが問題を引き起こしたり、何らかの目に見える視覚的な結果を生じたりする場合、それはあなたのコンポーネントのバグです! メモ化を追加するのではなく、バグを修正します。

state を更新する不要なエフェクトを避けてください。React アプリケーションのパフォーマンス問題の大部分は、エフェクト内での連鎖的な state 更新によってコンポーネントのレンダーが何度も引き起こされるために生じます。

エフェクトから不要な依存値をできるだけ削除します。例えば、メモ化する代わりに、オブジェクトや関数をエフェクトの中や外に移動させるだけで、簡単に解決できる場合があります。

custom hooks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const useLocalStorage = (key, defaultValue) => {
const [value, setValue] = useState(() => {
const jsonValue = window.localStorage.getItem(key);
if (jsonValue != null) return JSON.parse(jsonValue);
if (typeof initialValue === "function") {
return defaultValue();
} else {
return defaultValue;
}
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};

function App() {
const [age, setAge] = useLocalStorage("age", 24);
return (
<p>{age}</p>
<button onClick={() => setAge(34)}>年齢をセット</button>
);
}