前回は部品に型を付けました。今回はアプリの中心にあるデータに型を通します。
中心の型は1か所に置く
Bird型は、カード・フォーム・Context・詳細ページとあちこちで使います。こういう型は専用のファイルに置きます。
export type Bird = {
// JSONのidは数値、フォームで足した鳥は文字列のUUID
id: number | string
name: string
emoji: string
area: string
rare: number
memo: string
}fetchの戻り値はany——ここで検査が切れる
Contextのfetchに型を通します。
const [birds, setBirds] = useState<Bird[]>([])
useEffect(() => {
fetch('/birds.json')
.then((response) => response.json())
.then((data) => {
setBirds(data) // data は any
setIsLoading(false)
})
}, [])setBirdsはBird[]しか受け取らないのに、これはエラーになりません。response.json()の戻り値がanyだからです。
anyは「検査しない」という意味なので、そこで型の守りが切れます。JSONの中身がBirdの形をしている保証は、TypeScriptのどこにもありません。
対処は2段階あります。
// ①「こう扱う」と宣言する(実務でいちばん多い)
.then((data) => setBirds(data as Bird[]))
// ②本当に確かめる(外部APIならこちら)
.then((data) => setBirds(parseBirds(data))) // 自分で中身を検証する関数Contextの型——nullを型で表す
createContext(null)のままだと、前回のエラーにあったとおり「null型に値は入れられない」と怒られます。
type BirdsContextValue = {
birds: Bird[]
isLoading: boolean
addBird: (bird: Bird) => void
removeBird: (id: Bird['id']) => void
}
const BirdsContext = createContext<BirdsContextValue | null>(null)| nullが肝心です。Providerの外で使われたときはnullが返る——その事実を型で表しています。
そして読み出し口でnullを弾きます。
export function useBirds() {
const value = useContext(BirdsContext)
if (!value) {
throw new Error('useBirds は BirdsProvider の中で使ってください')
}
return value // ここから先は BirdsContextValue(nullではない)
}カスタムフックの戻り値は推論に任せる
useFavoritesには戻り値の型を書きません。
export function useFavorites() {
const [favoriteIds, setFavoriteIds] = useState<Bird['id'][]>([])
const toggleFavorite = useCallback((id: Bird['id']) => { … }, [])
return { favoriteIds, toggleFavorite } // 型は自動で決まる
}中でuseState<Bird['id'][]>と書いてあるので、戻り値の形はTypeScriptが計算できます。ここに手で型を書くと、中身を変えるたびに2か所直すことになります。
「入口(引数・state)に型を書き、出口は推論に任せる」——これが書きすぎないコツです。
findはundefinedを返す
詳細ページで、こういうエラーが出ます。
error TS18048: 'bird' is possibly 'undefined'.const bird = birds.find((item) => String(item.id) === id)findは見つからなければundefinedを返します。URLに存在しないidが来ることは実際にあるので、TypeScriptの指摘は正しいのです。
前回書いた早期returnがそのまま効きます。
if (!bird) {
return <p>その鳥は見つかりませんでした。</p>
}
// ここから下では bird は Bird(undefined ではない)通し終えたら、検査が緑になる
ここまででtscが通ります。
$ npx tsc -b
$ echo $?
0何も出力されないのが成功です。そしてnpm run buildも、型検査 → ビルドの順に走って通ります。
公式ドキュメントも見てみる
ja.react.dev外部サイトuseContext – React公式のuseContextリファレンス。TypeScriptと組み合わせるときの型の付け方(createContextのジェネリクス)にも触れていますこのレッスンのまとめ
- 中心のデータ型は
src/types.tsに1か所。部品専用の型はその場に置いたままでよい - 型は設計のほころびを見せる——
id: number | stringに気づけたのがその例 response.json()はany。そこで検査が切れるasは保証ではなく宣言。外部APIなら検証ライブラリで本当に確かめる- Contextは
createContext<値 | null>(null)、読み出しフックでnullを弾く - 入口に型を書き、出口(戻り値)は推論に任せる
findはundefinedを返す。早期returnで絞りこむ。ただしfunctionの中までは届かない
次回は最終回。型と長く付き合うコツ——anyとunknown、状態をユニオンで表す書き方、型エラーの読み方です。