コーディング · プログラミング言語
TypeScript入門チュートリアル(初心者向け)
結論
TypeScriptは静的型付けを備えたJavaScriptです。let count: number = 0 のように型注釈を書くと、コンパイラ tsc が型を検査し、ブラウザでもNode.jsでも動く通常のJavaScriptに変換します。基本の型、オブジェクトの形を表すインターフェースと型エイリアス、型の絞り込み(ナローイング)を伴うユニオン型、再利用できるコードのためのジェネリクスが要点です。型は実行時には消えます。
TypeScriptとは何か、何に使われるか
TypeScriptはMicrosoftが開発するオープンソースの言語で、2012年に最初のバージョンが公開されました。有効なJavaScriptはすべて有効なTypeScriptでもあるため、ファイル単位で段階的に導入できます。型チェッカーが、プロパティ名のタイプミスや null チェックの漏れなどを入力中に検出し、エディタは型情報を使って補完や安全なリネームを行います。大規模なフロントエンドアプリ(Angularはこれで書かれており、React、Vue、Svelteも十分に対応しています)、Node.jsのバックエンド、ライブラリで広く使われています。
インストールと Hello, world の実行
まずNode.jsをインストールし、プロジェクトにnpmでコンパイラを入れます。tsc は .ts ファイルを .js にコンパイルします。
const message: string = "Hello, world";
console.log(message);npm install --save-dev typescript
npx tsc --init # creates tsconfig.json
npx tsc hello.ts # writes hello.js
node hello.js
# Hello, world最近のNode.js、Deno、Bunは、型注釈を取り除くことで .ts ファイルを直接実行することもできます。ただし型チェックは行わないため、型エラーを見つけるには npx tsc --noEmit も実行してください。
基本の型と型推論
すべての型を書く必要はほとんどありません。変数を初期化すると、TypeScriptが型を推論します。let n = 5 は number 型です。関数の引数と公開APIには型注釈を付けましょう。
| 型 | 例 | 備考 |
|---|---|---|
number | let n: number = 3.5 | 整数と浮動小数点数 |
string | let s: string = "hi" | 文字列 |
boolean | let ok: boolean = true | true または false |
| 配列 | number[] または Array<number> | どちらの書き方も同じ意味 |
| タプル | [string, number] | 長さと各位置の型が固定 |
unknown | let x: unknown | 任意の値。使う前に確認が必要 |
any | let y: any | チェックを無効にする。避ける |
void / never | 関数の戻り値の型 | 何も返さない/決して戻らない |
let count = 0; // inferred as number
count = "ten"; // error: Type 'string' is not assignable to type 'number'
const names: string[] = ["Ann", "Bo"];
const pair: [string, number] = ["age", 31];2行目はコンパイルエラーになるようにわざと書いたもので、チェッカーの報告内容を示すためのものです。
関数、制御構文、型の絞り込み
制御構文(if、for、while、switch)はJavaScriptとまったく同じです。TypeScriptが加えるのは型の絞り込み(ナローイング)で、typeof x === "string" のような判定の後は、そのブランチ内でコンパイラがより具体的な型を把握します。
function greet(name: string, greeting = "Hello"): string {
return `${greeting}, ${name}!`;
}
function formatId(id: string | number): string {
if (typeof id === "number") {
return id.toFixed(0).padStart(6, "0"); // id is number here
}
return id.toUpperCase(); // id is string here
}
console.log(greet("Lin")); // Hello, Lin!
console.log(formatId(42)); // 000042
console.log(formatId("ab7")); // AB7string | number はユニオン型で、値が列挙した型のいずれかであることを表します。省略可能な引数は name?: string と書き、その型には undefined が含まれます。
インターフェースと型エイリアス
オブジェクトの形は interface または type エイリアスで記述します。TypeScriptは名前ではなく構造を検査するため、必要なプロパティを持つオブジェクトならどれでも適合します。
interface User {
id: number;
name: string;
email?: string; // optional
readonly createdAt: Date;
}
type Role = "admin" | "editor" | "viewer"; // string literal union
function describe(user: User, role: Role): string {
const contact = user.email ?? "no email";
return `${user.name} (${role}, ${contact})`;
}
const ann: User = { id: 1, name: "Ann", createdAt: new Date() };
console.log(describe(ann, "editor")); // Ann (editor, no email)特徴的な機能:ジェネリクスと判別可能なユニオン
ジェネリクスを使うと、型どうしの関連を保ったまま、1つの関数や型を多くの型で使えます。判別可能なユニオン(discriminated union)は、リテラル型の共通フィールドを持つオブジェクト型のユニオンです。そのフィールドで switch すると型が絞り込まれ、すべてのケースを処理しているかをコンパイラが検査できます。
function first<T>(items: T[]): T | undefined {
return items[0];
}
const n = first([10, 20]); // n: number | undefined
const s = first(["a", "b"]); // s: string | undefined
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rect":
return shape.width * shape.height;
}
}
console.log(n, s, area({ kind: "rect", width: 2, height: 3 })); // 10 a 6組み込みのユーティリティ型は、既存の型から新しい型を作ります。Partial<User> はすべてのプロパティを省略可能にし、Pick<User, "id" | "name"> は一部のプロパティだけを残し、Record<string, number> は辞書を表します。
エラー処理
エラーはJavaScriptと同じく throw と try/catch で扱います。strict オプションを有効にすると、どんな値でも throw できるため、捕捉した値の型は unknown になります。使う前に確認してください。
function parseAge(text: string): number {
const age = Number(text);
if (!Number.isInteger(age) || age < 0) {
throw new Error(`invalid age: ${text}`);
}
return age;
}
try {
parseAge("abc");
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
console.log(message); // invalid age: abc
}小さな完成プログラム:型付きの在庫管理
interface Item {
name: string;
price: number;
qty: number;
}
type Summary = { total: number; cheapest?: Item; outOfStock: string[] };
function summarize(items: Item[]): Summary {
let total = 0;
let cheapest: Item | undefined;
const outOfStock: string[] = [];
for (const item of items) {
total += item.price * item.qty;
if (item.qty === 0) outOfStock.push(item.name);
if (!cheapest || item.price < cheapest.price) cheapest = item;
}
return { total, cheapest, outOfStock };
}
const result = summarize([
{ name: "pen", price: 1.5, qty: 10 },
{ name: "book", price: 12, qty: 2 },
{ name: "bag", price: 30, qty: 0 },
]);
console.log(result.total, result.cheapest?.name, result.outOfStock);
// 39 pen [ 'bag' ]qty: 10 を qty: "10" に変えてみてください。コードを実行する前に、コンパイラがエラーを報告します。
初心者がよくやる間違い
- エラーを黙らせるために
anyを使う。unknownを使い、型を絞り込みましょう。 asによる型アサーションを使いすぎる。これはコンパイラに確認させず、こちらを信用させるものです。tsconfig.jsonでstrictを無効のままにする。新しいプロジェクトでは有効にしましょう。nullのチェックが働きます。- コンパイラがすでに推論できる型を書いてしまい、安全性は変わらないのに記述が増える。
次のステップ
公式のTypeScript Handbookを読み、typescriptlang.orgのTypeScript Playgroundで試してみましょう。コンパイル後のJavaScriptとエラーを並べて確認できます。その後、tsconfig.json のオプション、ライブラリの型宣言ファイル(@types/... パッケージ)、お好みのフレームワークでのTypeScriptの使い方を学びます。
よくある質問
TypeScriptはJavaScriptとは別の言語ですか?
JavaScriptのスーパーセットです。型の構文とチェッカーを追加し、通常のJavaScriptにコンパイルします。実行時の動作は、生成されたJavaScriptと同じです。
TypeScriptの前にJavaScriptを学ぶべきですか?
まずJavaScriptの基本(変数、関数、オブジェクト、Promise)を学んでください。TypeScriptはそれらを変えません。実行時の動作に慣れてからTypeScriptを加えると、型を覚えやすくなります。
interfaceとtypeの違いは何ですか?
どちらもオブジェクトの形を記述できます。インターフェースは拡張でき、再度宣言すると定義がマージされます。型エイリアスはユニオンやタプルなど、オブジェクト以外の型にも名前を付けられます。単純なオブジェクトの形ならどちらでも構いません。
TypeScriptでコードは速くなりますか?
いいえ。型はコンパイル時に取り除かれるため、出力は手書きのJavaScriptと同じ速度で動きます。メリットはバグの減少と、ツールの支援が強化されることです。