型が通りました。最終回は、型と長く付き合うためのコツです。ここは書き方の知識というより、判断の話になります。
書きすぎない
いちばん多い失敗は、全部に型を書くことです。
// ✕ 書きすぎ
const [keyword, setKeyword] = useState<string>('')
const shownBirds: Bird[] = birds.filter((bird: Bird): boolean => …)
// ○ 推論に任せる
const [keyword, setKeyword] = useState('')
const shownBirds = birds.filter((bird) => …)birdsがBird[]だと分かっていれば、filterの中のbirdも戻り値もTypeScriptが自動で追いかけます。手で書くと、Bird型を変えたときに直す場所が増えるだけです。
anyではなくunknown
エラーを消したいだけならanyが最短です。しかしanyは検査を止める指示で、その値は何をしても通ってしまいます。
const data: any = await response.json()
data.namee.toUpperCase() // 打ち間違いも通る → 実行時に落ちる代わりにunknownを使います。これは「中身が分からない」という表明で、使う前に確認を強制されます。
const data: unknown = await response.json()
if (Array.isArray(data)) {
setBirds(data as Bird[]) // 確認してから扱う
}| 意味 | 使うとき | |
|---|---|---|
any | 検査しない | 原則使わない。既存コードの移行中の一時避難だけ |
unknown | 中身が不明 | 外から来た値(APIの応答・JSON.parseの結果) |
状態は「ユニオン型」で表す
実務でいちばん効くのがこれです。いまの読みこみ処理は、こうなっています。
const [birds, setBirds] = useState<Bird[]>([])
const [isLoading, setIsLoading] = useState(true)一見よさそうですが、ありえない状態が作れてしまいます。
- 読みこみ中なのに
birdsが入っている - 読み終わったのに
birdsが空(失敗?それとも0件?) - 失敗を表す場所がない
そこで、状態を1つの値で表します。
type LoadState =
| { status: 'loading' }
| { status: 'error'; message: string }
| { status: 'success'; birds: Bird[] }
const [state, setState] = useState<LoadState>({ status: 'loading' })|で並べたこの形をユニオン型と呼びます。うれしいのは、statusで分岐すると中身の型も一緒に決まることです。
if (state.status === 'loading') return <p>よみこみ中…</p>
if (state.status === 'error') return <p>{state.message}</p>
// ここでは state.birds が必ずある
return <BirdList birds={state.birds} />loadingの枝でstate.birdsと書くと、TypeScriptが止めてくれます(そこには存在しないため)。
import typeを付ける
型だけを読みこむときはimport typeと書きます。
import type { Bird } from '../types.ts'
import { useState } from 'react'
import type { ChangeEvent } from 'react'設定でverbatimModuleSyntaxが有効なときは必須です(Viteのテンプレートは有効)。付けておくと、ビルド後のJavaScriptからその行が確実に消えるという利点もあります。
型エラーの読み方
長いエラーが出たときは、最初の1行と最後の1行だけ読みます。
error TS2345: Argument of type '(current: never[]) => any[]' is not assignable to
parameter of type 'SetStateAction<never[]>'.
Type '(current: never[]) => any[]' is not assignable to type '(prevState: never[]) => never[]'.
Type 'any[]' is not assignable to type 'never[]'.- 最初の行——「Aという型はBに入れられません」(何と何が食い違っているか)
- 下にぶら下がる行——その差がどこにあるかを掘り下げているだけ
- 最後の行——いちばん具体的な食い違い(ここでは
any[]とnever[])
この例は、useState([])と書いたせいでnever[]になっていたのが原因でした。エラー文の中に出てくる見慣れない型(neverなど)が、たいてい犯人です。
型が守ってくれないもの
最後に、正直な線引きを。
型は「書いてあることが正しいか」しか見ません。 実行時に本当に何が来るかは見ていません。
- APIが仕様と違う形を返した → 型は通るが落ちる
asで宣言した形が実物と違う → 型は通るが落ちる- ユーザーが想定外の入力をした → 型の管轄外
だから型を入れてもテストは要りますし、外部データの検証も要ります。「型があるから安全」ではなく、「型は、書き間違いという種類のバグを機械が拾ってくれる仕組み」——この距離感で付き合うのが、いちばん長続きします。
公式ドキュメントも見てみる
www.typescriptlang.org外部サイトDocumentation - Everyday TypesTypeScript公式ハンドブックの「Everyday Types」。ユニオン型・型エイリアス・anyなど、この章で使った型がまとまっています(英語)このレッスンのまとめ
- 入口に書き、出口は推論に任せる。同じ情報を2か所に書かない
anyは検査を止める指示で、しかも伝染する。外から来た値にはunknown- 状態はユニオン型で1つにまとめる——
isLoadingとerrorを別々に持たない - ユニオン型は「ありえない状態」を書けなくする。型の使い方でいちばん見返りが大きい
- 型だけの読みこみは
import type(verbatimModuleSyntaxでは必須) - 長いエラーは最初と最後の1行。見慣れない型(
neverなど)が犯人 - 型は実行時の中身を保証しない。外部データの検証とテストは別に必要
次の章は実務の道具立てです。データ取得のライブラリ、テスト、コードの置き場所、そしてNext.js——現場で当たり前に出てくるものを、ここまでの知識の上に載せていきます。