React Hooks
https://ja.react.dev/reference/react/hooks
React Component から呼び出されたときに、特別な機能を提供する関数
Hook は、React Component と同様に 純粋関数 であるべき
https://ja.react.dev/reference/rules/components-and-hooks-must-be-pure
React は、Hook が毎回同じ場所で呼び出されることに依存している
したがって、常に同じ順序で呼ばれる必要がある
理由
再レンダリング時に再び useState が呼び出されたときに、どの状態を返すべきかを特定しているため
https://ja.react.dev/learn/state-a-components-memory#how-does-react-know-which-state-to-return
React はすべてのコンポーネントに対して state のペアの配列を保持しています。
また、現在のペアインデックスも管理しており、レンダー前に 0 に設定されます。
useState が呼び出されるたびに、React は次の状態ペアを提供し、インデックスをインクリメントします。
https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e
簡易的な useState の実装
code:js
// React内部でのuseStateの動作
function useState(initialState) {
let pair = componentHookscurrentHookIndex;
if (pair) {
// 最初のレンダリングではないので、すでにstateのペアが存在している。
// それを返し、次のフック呼び出しに備える。
currentHookIndex++;
return pair;
}
// これは初めてのレンダリングなので、stateのペアを作成し、それを保存する。
pair = initialState, setState;
function setState(nextState) {
// ユーザーがstateの変更を要求したとき、新しい値をペアに設定する。
pair0 = nextState;
updateDOM();
}
// 今後のレンダリングのためにペアを保存し、次のフック呼び出しに備える。
componentHookscurrentHookIndex = pair;
currentHookIndex++;
return pair;
}
Hook はフックはトップレベルでのみ呼び出す というルールを守ると、Hook は常に同じ順序で呼ばれる
https://ja.react.dev/reference/rules/rules-of-hooks#only-call-hooks-at-the-top-level
逆に条件分岐内で Hook を呼び出すと、同じ順序で呼ばれなくなる
ESLint の eslint-plugin-react-hooks プラグインを入れると、違反しないように手助けしてくれる
https://www.npmjs.com/package/eslint-plugin-react-hooks
code:sh
$ npm install --save-dev eslint-plugin-react-hooks
code:eslint.config.js
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginReact from "eslint-plugin-react";
import eslintConfigPrettier from "eslint-config-prettier";
import eslintPluginReactHooks from "eslint-plugin-react-hooks";
export default [
{ files: "**/*.{js,mjs,cjs,ts,jsx,tsx}", ignores: "dist" },
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
...pluginReact.configs.flat.recommended,
settings: { react: { version: "detect" } },
plugins: {
"react-hooks": eslintPluginReactHooks,
},
rules: {
"react/react-in-jsx-scope": "off",
...eslintPluginReactHooks.configs.recommended.rules,
},
},
eslintConfigPrettier,
];
2024 年 9 月時点で、Flat Config 向けの共有設定(Shareable Configs)が提供されていないため、少々設定方法が異なる
https://qiita.com/Yasushi-Mo/items/9f98f1b79fe1544c6a59
Hook の呼び出す順序が一貫している限り、直接 Component から呼び出されるか、他の関数を通じて間接的に呼び出されるかは問題でない
これより、Hook は React の関数からのみ呼び出す というルールが導ける
https://ja.react.dev/reference/rules/rules-of-hooks#only-call-hooks-from-react-functions
カスタムフック: React Hooks#66d84f5075d04f0000e37b45
カスタムフック
https://ja.react.dev/learn/reusing-logic-with-custom-hooks
他の Hook を呼び出す関数も Hook として扱って 呼び出される順序を保持する 必要がある
このような Hook がカスタムフック
慣例的に use という接頭辞を関数名に付ける
e.g.
code:useSlideIndex.tsx
import { useState } from "react";
export const useSlideIndex = () => {
const slideIndex, setSlideIndex = useState(0);
return slideIndex, setSlideIndex as const;
};
as const
readonly(変更不可)なリテラル型であることを示す
const useSlideIndex: () => readonly [number, React.Dispatch<React.SetStateAction<number>>]
付けないと、 (number | React.Dispatch<React.SetStateAction<number>)[] と解釈される
TypeScript はより汎用的な型に推論するため
#React