前回はエラーの山を作ったところで終わりました。今回から埋めていきます。まずはprops・state・イベントの3つです。
propsの型——type Propsが定番
いちばん多いエラー(Binding element 'bird' implicitly has an 'any' type.)から片づけます。
import type { Bird } from '../types.ts'
type Props = {
bird: Bird
isFavorite: boolean
onToggleFavorite: (id: Bird['id']) => void
}
function BirdCard({ bird, isFavorite, onToggleFavorite }: Props) {
…
}読み方はこうです。
type Props = { … }——このコンポーネントが受け取るものの一覧: Props——引数がその形であることを宣言(id: Bird['id']) => void——「idを1つ受け取って、何も返さない関数」
3つ目が関数の型です。=>の右が戻り値で、voidは「返さない」という意味。この書き方に慣れると、他人のコードのonXxxが何を渡してくるか、実装を読まなくても分かるようになります。
childrenを受け取るときはReactNode
タグで挟んだ中身を受け取る部品には、ReactNodeという型を使います。
import type { ReactNode } from 'react'
export function BirdsProvider({ children }: { children: ReactNode }) { … }ReactNodeは「Reactが画面に出せるもの全部」——JSX・文字列・数値・配列・null——をまとめた型です。「何が来るか分からないけど画面に出すもの」には、これを使っておけば間違いありません。
useState——推論に任せる/明示する
useStateは初期値から型を推論します。
const [keyword, setKeyword] = useState('') // string と推論される
const [onlyFavorites, setOnlyFavorites] = useState(false) // booleanこの2つに型は書きません。 書いても同じで、読みにくくなるだけです。
問題は空の配列です。
const [birds, setBirds] = useState([]) // ✕ never[] と推論される空の配列からは「中身が何か」を推論しようがないので、TypeScriptはnever[](何も入れられない配列)と判断します。実際、前回のエラーにこれが出ていました。
error TS2322: Type '{ birds: never[]; … }' is not assignable to type 'null'.
error TS2339: Property 'id' does not exist on type 'never'.中身の型を教えます。
const [birds, setBirds] = useState<Bird[]>([]) // ○イベントの型
eventにも型が要ります。使うのはほぼ2種類です。
import type { ChangeEvent, FormEvent } from 'react'
function handleSubmit(event: FormEvent<HTMLFormElement>) { … }
<input onChange={(event: ChangeEvent<HTMLInputElement>) => setName(event.target.value)} />
<select onChange={(event: ChangeEvent<HTMLSelectElement>) => setEmoji(event.target.value)} /><…>の中はイベントが起きた要素です。HTMLInputElementだとevent.target.valueが使え、HTMLSelectElementなら選択値が取れます。ここを正しく書くと、event.target.まで打った時点で候補が出ます。
useRefとnull
前回のエラーにこれがありました。
error TS18047: 'nameInputRef.current' is possibly 'null'.useRef(null)のcurrentは、画面が描かれるまでnullです。TypeScriptはそれを知っているので、いきなり.focus()を呼ぶと止めてくれます。
const nameInputRef = useRef<HTMLInputElement>(null)
nameInputRef.current?.focus() // ○ nullなら何もしないuseRef<HTMLInputElement>(null)——「input要素が入る予定の入れ物」?.(オプショナルチェーン)——nullのときは呼ばずに素通りする
formsの回で「.currentは最初null」と書きましたが、TypeScriptを入れると、その注意を機械が代わりに見張ってくれます。これが型を入れる効きめのひとつです。
公式ドキュメントも見てみる
ja.react.dev外部サイトTypeScript の使用 – React公式の「TypeScriptの使用」。propsの型・useState・イベントハンドラの書き方が、この回と同じ順で並んでいますこのレッスンのまとめ
- propsは
type Props = { … }を作って: Propsで受け取る(interfaceでも可) - 関数のpropsは
(id: Bird['id']) => voidの形。=>の右が戻り値 childrenはReactNodeuseStateは推論に任せる。空配列・nullなど推論できないときだけ<T>を書く- イベントは
ChangeEvent<HTMLInputElement>・FormEvent<HTMLFormElement>。名前はエディタに聞く - JSXの中のインライン関数は、型を書かなくても推論されることが多い
useRef<HTMLInputElement>(null)のcurrentはnullかもしれない——?.で守る
次回はデータそのものの型です。Bird型を1か所に置いて、Contextとカスタムフックにも型を通します。