コース一覧
React状態管理:カスタムフック・Context・Reducer
フックのルール

React状態管理:カスタムフック・Context・Reducer

カスタムフック、フックのルール、Context、useContext、useReducer、Actionのunion型を扱う全21レッスンの状態管理コースです。fleamaの商品・フォーム・テーマ・ユーザー・カート状態を、再利用可能なロジックと予測可能な状態遷移へ整理します。

1
カスタムフック
01. なぜカスタムフックか10分
02. useItemsを作る15分
03. useFormを作る15分
04. フックのルール15分
05. フックに型を付ける15分
06. つくる - フック化完了20分
07. 第4章クイズ15分
2
useContext
01. バケツリレーの限界15分
02. Contextを作る15分
03. useContextで使う15分
04. テーマの全体共有15分
05. ユーザー情報の共有15分
06. つくる - Context整備20分
07. 第5章クイズ15分
3
useReducer
01. setStateの散らかり15分
02. useReducer15分
03. Actionのunion型15分
04. カートreducer15分
05. 使い分け10分
06. つくる - カート完成20分
07. 第6章クイズ15分

フックのルール

フックを呼ぶ順番

フックには2つのルールしかない

自分でフックを作れるようになると、置き場所を自分で決めることになります。守るのは次の2つだけです。

  1. フックは関数のいちばん上の階層で呼ぶ。条件分岐、繰り返し、入れ子の関数の中では呼ばない
  2. フックを呼べるのは、コンポーネントか、use で始まる名前の関数の中だけ

なぜ順番が大事なのか

React は「何番目に呼ばれた useState か」で state を見分けています。名前ではなく順番です。

TypeScript (JSX)

const [a, setA] = useState(""); // 1番目 const [b, setB] = useState(0); // 2番目

ここで1番目を if で囲むと、条件が偽になった回だけ呼ばれる数が変わります。すると2番目だったものが1番目に繰り上がり、React は前の回の1番目、つまり別の state を返します。中身が入れ替わるか、Rendered fewer hooks than expected というエラーで画面ごと落ちます。

たちが悪いのは、条件がずっと真のあいだは何事も無く動いてしまうことです。数か月後に誰かが条件を変えた日に、まったく関係のない場所が壊れます。

条件はフックの中に入れる

「特定のときだけ処理したい」は、フックを囲むのではなく、フックの中で分けます。

TypeScript (JSX)

useEffect(() => { if (category === "") return; fetch("/api/items?category=" + category); }, [category]);

これなら useEffect は毎回同じ回数だけ呼ばれ、実際にやることだけが変わります。早期 return も同じです。フックより上に return を置くと、その回だけ呼ばれる数が減ります。フックを全部呼び終えてから return すると覚えてください。

名前が use で始まらない関数

function itemsFetcher() の中で useState を呼んでも、動いてしまうことがあります。ただし React から見ればそれはただの関数で、ふつうの関数として2回呼ばれたり、条件の中で呼ばれたりした瞬間に壊れます。検査の道具も名前でフックを見分けているので、警告すら出ません。フックを呼ぶ関数の名前は use で始める。 これはルールです。

演習

ルールを2つとも破っているコードを、動く形のまま直します。

ヒント

フックを呼ぶ関数の名前は use で始めます

category は "shoes" か "tops" のどちらかしか入らないので、空文字の判定は消して構いません

useEffect の依存配列に category を残しておくと、タブを押したときに取り直せます

App.tsx
mock-api.json
プレビュー

できているか

  • 最初にくつの一覧が2件出ている
  • トップスのタブを押すと一覧が入れ替わる
  • 名前が use で始まっている
  • use で始まらない古い名前が残っていない
  • useEffect を囲んでいた if が消えている
エディタを読み込んでいます