React・TypeScriptで書く

プロジェクトをTypeScriptに移行する

現場のReactはほぼTypeScriptです。すでに動いているプロジェクトに後から型を入れる、という実務でいちばん多い形で導入します。拡張子を変えた瞬間に出る大量のエラーが、なぜ「正常」なのかも扱います。

ここからは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.ts

importしているパスの拡張子も、あわせて直します('./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「これはnullundefinedかもしれない」存在チェックを足す
TS2339「その名前のプロパティは無い」型を定義する/打ち間違いを直す

implicitly has an 'any' type(暗黙のany)が大半です。これは「型を書かなかったので何でも入る扱いになるけれど、それでいいの?」という確認で、tsconfigstrictが効いている証拠でもあります。

公式ドキュメントも見てみる

ja.react.dev外部サイトTypeScript の使用 – React公式の「TypeScriptの使用」。Reactの型がどこから来るのか(@types/react)と、この章で扱う書き方がひととおり載っています

このレッスンのまとめ

  1. 実務のReactはほぼTypeScript。既存コードを読むために事実上必要
  2. 効いてくるのは書くときではなく、直すとき・増やすとき
  3. 導入はtypescriptを入れて、tsconfigを3枚置いて、拡張子を変えるだけ
  4. tsc検査係noEmit: true)。変換はViteがやる
  5. buildtsc -b && vite buildにすると、型エラーで公開が止まる
  6. 拡張子を変えた直後のエラーの山は正常。アプリは動いている
  7. 大半は暗黙のanyanyで黙らせずに、次回から型を書いて消していく

次回から、実際に型を書きます。まずはprops・state・イベントからです。

やってみよう:TypeScriptを入れて、エラーの山を見る

まだ型は書きません。移行して、エラーが出るところまでやります。

  1. npm install --save-exact -D typescript @types/node
  2. tsconfig.jsontsconfig.app.jsontsconfig.node.jsonの3枚を置く(Viteのreact-tsテンプレートの中身をそのまま)
  3. package.jsonbuildtsc -b && vite buildに変える
  4. ファイルの拡張子を.tsx / .tsに変え、importのパスもそろえる
  5. npx tsc -bを実行して、エラーの一覧を眺める
  6. npm run devでブラウザを開き、アプリが今までどおり動くことを確認する
npx tsc -b

6番が今回のいちばん大事な確認です。型エラーが20個出ていても、アプリは壊れていません。 TypeScriptは「動くかどうか」ではなく「書き方が説明できているか」を見ているだけ——この感覚を持ってから次に進むと、エラーの山がこわくなくなります。

よくある質問

ReactにTypeScriptは必要ですか?
個人の小さなアプリなら必須ではありません。ただ実務のReactはほぼTypeScriptで書かれているため、既存コードを読む・チームに入るという意味では事実上必要です。求人票でも前提として書かれていることが多い言語です。
最初からTypeScriptで始めなかったのはなぜですか?
型のエラーとReactの間違いが同時に出ると、初学者はどちらが原因か切り分けられなくなるためです。Reactの考え方が身についたあとで型を足すほうが、結果的に速く進めます。
拡張子を変えたら大量にエラーが出ました
正常です。型注釈が無いところをTypeScriptが順に指摘しているだけで、アプリは動いたままです。ファイルを1つずつ直していけば減っていきます。
tsconfig.jsonは自分で書くのですか?
ゼロから書く必要はありません。Viteのreact-tsテンプレートが生成する3ファイルをそのまま持ってくるのが確実で、実務でもテンプレートの設定から始めるのが普通です。