3状態を型で表す

通信には3つの結末がある

第2章で、商品一覧を API から取れるようになりました。動くには動きますが、いまの画面には足りないものがあります。

const [items, setItems] = useState<Item[]>([]);

この形だと、読み込んでいる途中も、通信に失敗したときも、本当に0件のときも、使う人には全部「空っぽの一覧」に見えます。3つの違う出来事が、1つの見た目につぶれています。

旗を足すと組み合わせが増える

よくある直し方は、旗になる state を足すことです。

const [items, setItems] = useState<Item[]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState("");

これでも一応は書けます。ただ、この3つは別々に動くので「読み込み中なのにエラー文も出ている」「成功しているのに前のエラーが残っている」といった、ありえない組み合わせまで表せてしまいます。旗が増えるほど、書き忘れた組み合わせが画面に出ます。

1つのユニオン型にまとめる

取りうる姿が3つなら、その3つをそのまま型に書きます。

type LoadState = | { status: "loading" } | { status: "success"; items: Item[] } | { status: "error"; message: string };

TypeScript入門で出てきた判別可能なユニオン型です。共通の目印 (ここでは status) で枝を見分けます。3つのうちどれか1つにしかなれないので、ありえない組み合わせはそもそも書けません。

枝の中でしか中身を読めない

{state.status === "loading" && <p className="loading">読み込み中</p>} {state.status === "error" && <p className="error">{state.message}</p>} {state.status === "success" && <div className="list">{/* 一覧 */}</div>}

state.items が読めるのは success の枝の中だけです。読み込み中に items を触るコードは、書いた時点で赤くなります。状態の抜け漏れを、目で見て気を付けるのではなく型で潰すのがこの回の要点です。

演習

一覧の取得を LoadState で書き直し、読み込み中と成功を出し分けます。

ヒント

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

できているか

  • 取得の前は読み込み中が出ている
  • 取得できたら5件並ぶ
  • 商品名が出ている
  • 価格に 円 が付いている