型と付き合っていく——次のステップ
レッスン8:型と付き合っていく——次のステップ
このレッスンで学ぶこと
- コンパイルエラーのメッセージを落ち着いて読めるようになる
- 型定義ファイルという仕組みの存在を知る
- フレームワークで型がどう使われるかを概観する
- 型を書きすぎないという判断の基準を持つ
レッスン7 では、ジェネリクスと、既存の JavaScript コードへ少しずつ型を足していく手順を学びました。ここまでの 7 つのレッスンで、型注釈・型推論・インターフェース・クラス・モジュール・ジェネリクスという、TypeScript の主要な要素をひととおり見てきました。最後のレッスンでは、これから実際にコードを書き続けていくうえで役立つ、いくつかの実践的な視点を確認します。
新しい構文を学ぶことよりも、ここから先で重要になるのは、学んだ構文とどう長く付き合っていくかという視点です。エラーメッセージとの向き合い方、他人が書いたコードとの付き合い方、そして自分が書く型の量そのものの調整——いずれも、構文の知識だけでは身につかない、経験の中で磨かれていく感覚です。このレッスンでは、その感覚を先取りして紹介します。
エラーメッセージの読み方
レッスン1 から、TypeScript のエラーメッセージを何度も見てきました。最初は英語の専門用語が並んでいて身構えてしまうかもしれませんが、多くのエラーメッセージは、決まった型に沿って情報を伝えています。改めて、代表的なエラーの形を整理してみましょう。
error TS2322: Type 'number' is not assignable to type 'string'.
このメッセージは「型番号:エラーの種類」「型Aは型Bに代入できません」という 2 つの部分で構成されています。TS2322 の部分はエラーの種類を表す番号で、同じ種類の間違いには常に同じ番号が付きます。検索エンジンでこの番号を調べると、同じ間違いに遭遇したほかの開発者の説明が見つかることも多く、意味がつかみにくいときの手がかりになります。
error TS2339: Property 'nmae' does not exist on type 'User'. Did you mean 'name'?
このメッセージのように、TypeScript は綴りの近いプロパティ名を見つけると、「もしかして name の間違いではありませんか」と候補まで提示してくれることがあります。エラーメッセージを最後まで読むと、修正のヒントがすでに書かれている場合が少なくありません。
引数の数が合わないときのエラーも、よく遭遇する形の 1 つです。
function greet(name: string, honorific: string) {
console.log(`${honorific} ${name}さん`);
}
greet("森田");
error TS2554: Expected 2 arguments, but got 1.
「2 つの引数が期待されているのに、1 つしか渡されていません」という指摘です。レッスン4 で学んだ省略可能な引数(?)やデフォルト値を付け忘れていないか、あるいは単純に渡し忘れていないかを確認します。数値で示されたエラーの種類そのものよりも、「期待されている個数」と「実際に渡された個数」という 2 つの数字を読み比べる習慣を付けておくと、原因を素早く特定できます。
エラーメッセージを読むときの基本的な手順は、次の 3 つです。
- エラーが起きている行と列の番号を確認する:エディタがエラー箇所に赤い波線を引いてくれるので、まずそこにカーソルを合わせる
- 「型Aは型Bに代入できない」のように、期待されている型と実際の型を読み分ける:どちらが期待値で、どちらが実際の値かを取り違えないようにする
- 1 つのエラーを直したら、再度コンパイルする:1 つの間違いが、後続の複数のエラーの原因になっていることがあるため、直すたびに確認する
🔰 初学者の方へ エラーメッセージがたくさん表示されると、圧倒されてしまうことがあります。そんなときは、一番上(多くの場合、一番最初に発生した根本的な原因に近い)のエラーから 1 つずつ順番に対応してみてください。1 つ直すだけで、芋づる式にほかのエラーも解消することがよくあります。慌てて一度にすべてを直そうとせず、コンパイルと修正を小刻みに繰り返すほうが、結果的に早く解決できます。
型定義ファイルという存在
これまでのレッスンでは、自分で書いたコードに自分で型を付けてきました。しかし実務では、ほかの人が作った JavaScript のライブラリを、TypeScript のプロジェクトの中で使う場面が数多くあります。もとの JavaScript のライブラリ自体には型情報がなくても、そのライブラリの形だけを別途記述した「型定義ファイル」(拡張子 .d.ts)を用意することで、TypeScript のプロジェクトから型の恩恵を受けながら使えるようになります。
// mathUtils.d.ts(型定義ファイルの例)
export function double(value: number): number;
.d.ts ファイルには、関数やクラスの「形」だけが書かれ、処理の中身({ } の中の実装)は書きません。実際の処理は、もとの JavaScript ファイルの中にすでにあるためです。型定義ファイルは、いわば「このライブラリの取扱説明書」のような役割を果たします。
利用者の側から見ると、型定義ファイルの存在を意識することはほとんどありません。
import { double } from "mathUtils";
const result = double("10"); // 型定義ファイルのおかげでエラーになる
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
mathUtils はもともと型を持たない JavaScript のライブラリだったとしても、対応する .d.ts ファイルが用意されていれば、import した時点でレッスン4 までに学んだのと同じ引数チェックが働きます。自分で書いたコードにも、外部のライブラリにも、同じ感覚で型のチェックが及ぶという点が、TypeScript の型システムの一貫性を支えています。
広く使われている JavaScript のライブラリの多くは、ライブラリ自体か、それを補うコミュニティの型定義パッケージのどちらかを通じて、こうした型定義があらかじめ用意されています。ライブラリを npm install で追加したときに、型定義も一緒にインストールされることが多く、多くの場合は利用者が .d.ts ファイルを自分で書く必要はありません。「JavaScript の資産には、後から型の説明書を付け足せる」という仕組みがある、という点をここでは押さえておいてください。
フレームワークでの型の使われ方——概観
「JavaScript 入門」のレッスン8 で紹介された React・Vue のようなフロントエンドのフレームワークの多くは、TypeScript と組み合わせて使われることを前提に作られており、画面の部品が受け取るデータの形を、レッスン5 で学んだインターフェースで定義するのが一般的です。
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean;
}
ButtonProps のようなインターフェースで、画面の部品に渡すデータの形をあらかじめ決めておくと、渡し忘れや型の不一致を、レッスン1 で見たのと同じように実行前に検出できます。本コースではフレームワークそのものの使い方までは扱いませんが、ここまでのレッスンで学んだ型注釈・インターフェース・ジェネリクスといった基礎知識は、そのままフレームワークの学習の土台になります。フレームワーク特有の書き方を新しく覚える必要はあっても、「型とは何か」「なぜ必要か」という考え方自体を学び直す必要はありません。
型を書きすぎないという判断
ここまでのレッスンでは、型を足すことのメリットを繰り返し確認してきました。最後に、あえてバランスを取るための視点を加えておきます。すべての値に、可能な限り厳密な型を付けることが、常に最善とは限りません。
function add(a: number, b: number): number {
return a + b;
}
const result: number = add(1, 2);
add(1, 2) の戻り値は、レッスン3 で学んだ型推論によって、すでに number だとわかっています。const result: number = ... の型注釈は、間違いではありませんが、なくても安全性は変わりません。中核メッセージの 1 つである「型は全部書かなくてよい。推論に任せられるところは任せる」を、コースの最後にもう一度思い出してください。
型を書きすぎることで起きる具体的な問題もあります。過剰に複雑なジェネリクスや、何重にも入れ子になったユニオン型は、書いた本人以外が読み解くのに時間がかかり、かえって「コードが意図を語る」という本コースの背骨に反してしまいます。型注釈は、レッスン1 で確認したとおり読み手のための説明書です。説明書自体が難解では、本来の目的を果たせません。
function formatLabel(
value: string | number | boolean | null | undefined,
options?: { prefix?: string; suffix?: string } | string
): string {
// ...
return "";
}
interface LabelOptions {
prefix?: string;
suffix?: string;
}
function formatLabel(value: string, options?: LabelOptions): string {
// ...
return "";
}
1 つ目の formatLabel は、あらゆる可能性を型に詰め込もうとした結果、引数の形そのものが読み取りにくくなっています。2 つ目のように、実際に使われる範囲へ型を絞り込み、複雑になった部分は LabelOptions のようなインターフェースに切り出すと、同じ柔軟さを保ちながら読みやすさを取り戻せます。「表現できる可能性を広げる」ことと「実際に使う形を明確にする」ことは、必ずしも同じ方向を向いていません。
この判断はレッスンを重ねるごとに自然と身についていくものであり、最初からすべてを見極められる必要はありません。むしろ、「型を書きすぎてしまった」という経験を一度でも持っておくと、次からの判断が的確になります。書きすぎに気づいたら、レッスン5 の interface にまとめ直す、レッスン7 のジェネリクスで共通部分をくくり出す、といった、これまでのレッスンで学んだ道具を使って整理し直せば十分です。
判断の目安をまとめると、次のようになります。
- 関数の引数、公開するモジュールの境界、外部から来るデータには、しっかり型を付ける
- 値を代入するだけの変数、コンテキストから明らかな戻り値には、型推論に任せる
- 型注釈そのものが複雑になりすぎたら、一度シンプルな形に戻せないか見直す
💡 ポイント 「型を厳密にすることが目的化していないか」を、時々立ち止まって確認する習慣を持つと、読みやすいコードを保てます。型は手段であって、目的ではありません。
コース全体の振り返り
レッスン1 で提示した 6 つの中核メッセージを、最後にもう一度振り返ります。
- 型は書き手を縛るためではなく、読み手に意図を伝えるためにある:レッスン2 から 5 まで、型注釈・インターフェースという形で繰り返し確認しました
- エラーは実行する前に見つけたほうが安い:レッスン1 の 4 つの事故と、各レッスンのコンパイルエラーの例で体験しました
- 型は全部書かなくてよい。推論に任せられるところは任せる:レッスン3 の型推論と、このレッスンで扱った「書きすぎない判断」で扱いました
anyは逃げ道であって、使うたびに型の恩恵が消える:レッスン2 でanyの危険性と、代わりとなるunknownを確認しました- インターフェースは「この形をしていること」だけを求める:レッスン5 の構造的型付けと、レッスン6 の
implementsで具体的に扱いました - 型を足すのは一度に全部ではなく、端から少しずつでよい:レッスン7 の既存コードへの移行手順で、実践的な進め方を確認しました
6 つのメッセージすべてに共通しているのは、「型は完璧を目指す道具ではなく、コードを読む人(未来の自分を含みます)とのコミュニケーションの道具である」という考え方です。この視点さえ持っていれば、TypeScript の細かい構文を忘れてしまっても、そのつど調べ直せば十分に使いこなせます。
レッスン1 で見た 4 つの事故——プロパティ名の綴り違い、undefined の紛れ込み、引数の順番の取り違え、そして typeof null が "object" になるという仕様の落とし穴——を思い出してください。これらはすべて、「コードを書いている時点では気づけず、実行して初めて発覚する」という共通点を持っていました。本コースで学んだ型注釈・型推論・インターフェース・クラス・ジェネリクスは、どれもこの「実行する前に気づく」という 1 点のために存在しています。技術要素としては多岐にわたりますが、目指している方向は一貫しています。
修了後の学習方向
本コースで、TypeScript の主要な要素をひととおり学び終えました。ここから先の学習の方向を、いくつか紹介します。
1. 自分のコードに型を足してみる:「JavaScript 入門」で作った ToDo リストや、そのほかの自分のコードを、実際に .ts ファイルに変えてみてください。レッスン7 で学んだ手順を、自分の手でもう一度たどることが、もっとも力になります。
2. 型定義ファイルを読んでみる:使っているライブラリの .d.ts ファイルを開いてみると、そのライブラリがどんな型のインターフェースを公開しているかがわかります。最初は読みにくく感じても、このレッスンで学んだインターフェースとジェネリクスの知識があれば、少しずつ読み解けるようになります。
3. フレームワークに進む:フロントエンドのフレームワークを学ぶ際、公式のドキュメントには TypeScript 向けの説明が用意されていることが多くあります。本コースの基礎を持っていれば、その説明を無理なく読み進められます。
4. より高度な型の書き方を学ぶ:本コースでは、ジェネリクスの基礎と代表的なユーティリティ型までを扱いました。TypeScript にはこの先にも、条件によって型を変化させる高度な仕組みがありますが、まずは本コースで身につけた基礎を、実際のコードで使い込むことを優先してください。
5. 公式のハンドブックを読む:本コースで扱いきれなかった細かい仕様は、公式サイトのハンドブックに体系的にまとまっています。本コースの基礎を持っていれば、必要な項目を辞書のように調べながら読み進められます。
🔰 初学者の方へ 「JavaScript 入門」のレッスン8 でも触れられていたとおり、プログラミングは「わかった」と「使える」の間に大きな差があります。本コースで学んだ型注釈・型推論・インターフェース・クラス・ジェネリクスも、実際に自分のコードで何度も使ってみることで、初めて身についた実感が持てるようになります。
まとめ
このレッスンでは、以下のことを学びました。
- コンパイルエラーは「エラーの種類」「期待される型と実際の型」を順に読み解くと対応しやすい
- ライブラリの形を記述した型定義ファイル(
.d.ts)によって、JavaScript の資産にも後から型の説明書を付けられる - フロントエンドのフレームワークは、インターフェースや型エイリアスと組み合わせて使われることが多い
- 型は全部を厳密にすることが目的ではなく、読み手に意図を伝えるための手段である
- コース全体を通じた 6 つの中核メッセージを振り返り、今後の学習の方向を確認した
「TypeScript とは何か」から始まった全 8 レッスンの学習、ここまでお疲れさまでした。手元には、型という説明書をコードに書き込む力が残っているはずです。レッスン1 で見た 4 つの小さな事故から出発し、型注釈・型推論・インターフェース・クラス・モジュール・ジェネリクスと、少しずつ積み重ねてきた知識は、どれも独立した知識ではなく、「コードに意図を書き込む」という 1 つの目的でつながっています。
最後の総復習テストで、コース全体の理解を確認してみてください。型との付き合いは、ここからが本番です。少しずつ、自分のペースで育てていってください。応援しています。
確認クイズ
このレッスンの理解度をチェックしましょう。