ここからはTypeScriptです。ことりずかんに型を付けていきます。
なぜ実務はTypeScriptなのか
理由は4つあります。
- 既存コードが読めない問題——現場のReactはほぼTypeScript。型が読めないと、他人のコードに手を入れられません
- エディタが教えてくれる——
bird.と打った時点で候補が出ます。名前の打ち間違いは書いている最中に赤くなります - 壊れる前に気づける——propsを増やしたら、渡し忘れている場所がその場で全部表示されます
- 書きかえがこわくなくなる——名前の変更や引数の追加を、機械が追いかけてくれます
途中から入れるのが、いちばん実務に近い
このコースは20回目までJavaScriptで書いてきました。これは実務でよくある状況そのものです。動いているJavaScriptのプロジェクトに、後から型を入れる——今からやるのはその作業です。
TypeScriptはJavaScriptの上に型を足したものなので、既存のコードはそのまま動きます。拡張子を変えて、型を書き足していくだけです。
①TypeScriptを入れる
npm install --save-exact -D typescript @types/node-D(開発用)なのは、型はビルド時に消えるからです。ブラウザに届くJavaScriptに型は1文字も含まれません。
②設定ファイルを置く
TypeScriptにはtsconfig.jsonという設定ファイルが要ります。ゼロから書く必要はありません。Viteが用意しているreact-tsテンプレートの中身をそのまま持ってきます。
置くのは3ファイルです。
shima-zukan/
├── tsconfig.json ← 2つを束ねるだけの薄いファイル
├── tsconfig.app.json ← src/ 用(ほぼこれが本体)
└── tsconfig.node.json ← vite.config.ts 用{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}tsconfig.app.jsonの中身は長いので全部は載せませんが、目を通しておきたい行だけ挙げます。
{
"compilerOptions": {
"jsx": "react-jsx", // JSXを書ける
"noEmit": true, // 変換はViteがやるので、tscは検査だけ
"moduleResolution": "bundler",
"verbatimModuleSyntax": true, // 型のimportは import type と書く
"noUnusedLocals": true, // 使っていない変数を怒る
"noUnusedParameters": true
},
"include": ["src"]
}noEmit: trueが大事です。 TypeScriptはJavaScriptを書き出しません。実際の変換はViteがやり、tscは検査係に徹します。役割が分かれていることを知っておくと、後で混乱しません。
③検査をビルドに組みこむ
package.jsonのビルドコマンドを差しかえます。
{
"scripts": {
"build": "tsc -b && vite build"
}
}tsc -bで型の検査をしてから、vite buildで書き出す——という順番です。型エラーが1つでもあると、ビルドがそこで止まります。これが「壊れたものを公開しない」仕組みになります。
④拡張子を変える
ここからが本番です。JSXを含むファイルは.tsx、含まないファイルは.tsにします。
src/App.jsx → src/App.tsx
src/main.jsx → src/main.tsx
src/pages/*.jsx → src/pages/*.tsx
src/components/*.jsx → src/components/*.tsx
src/context/BirdsContext.jsx → .tsx
src/hooks/useFavorites.js → src/hooks/useFavorites.ts ← JSXが無いので .ts
vite.config.js → vite.config.tsimportしているパスの拡張子も、あわせて直します('./App.jsx' → './App.tsx')。
⑤エラーの山を見る
この状態で検査すると、こうなります。
$ npx tsc -b
src/components/AddBirdForm.tsx(3,24): error TS7031: Binding element 'onAdd' implicitly has an 'any' type.
src/components/AddBirdForm.tsx(10,25): error TS7006: Parameter 'event' implicitly has an 'any' type.
src/components/AddBirdForm.tsx(30,5): error TS18047: 'nameInputRef.current' is possibly 'null'.
src/components/BirdCard.tsx(4,21): error TS7031: Binding element 'bird' implicitly has an 'any' type.
…(この時点で20件以上)20件を超えるエラーが一気に出ます。 そして、ここが大事なのですが——
エラーの種類は、実は少ない
さっきの山を分類すると、ほとんどが3種類です。
| エラー番号 | 言っていること | 直し方 |
|---|---|---|
| TS7031 / TS7006 | 「この引数の型が書かれていない」 | 型注釈を足す(次回) |
| TS18047 / TS18048 | 「これはnull/undefinedかもしれない」 | 存在チェックを足す |
| TS2339 | 「その名前のプロパティは無い」 | 型を定義する/打ち間違いを直す |
implicitly has an 'any' type(暗黙のany)が大半です。これは「型を書かなかったので何でも入る扱いになるけれど、それでいいの?」という確認で、tsconfigのstrictが効いている証拠でもあります。
公式ドキュメントも見てみる
ja.react.dev外部サイトTypeScript の使用 – React公式の「TypeScriptの使用」。Reactの型がどこから来るのか(@types/react)と、この章で扱う書き方がひととおり載っていますこのレッスンのまとめ
- 実務のReactはほぼTypeScript。既存コードを読むために事実上必要
- 効いてくるのは書くときではなく、直すとき・増やすとき
- 導入は
typescriptを入れて、tsconfigを3枚置いて、拡張子を変えるだけ tscは検査係(noEmit: true)。変換はViteがやるbuildをtsc -b && vite buildにすると、型エラーで公開が止まる- 拡張子を変えた直後のエラーの山は正常。アプリは動いている
- 大半は暗黙のany。
anyで黙らせずに、次回から型を書いて消していく
次回から、実際に型を書きます。まずはprops・state・イベントからです。