cv.cm

コーディング · プログラミング言語

TypeScript入門チュートリアル(初心者向け)

約10分で読めます

結論

TypeScriptは静的型付けを備えたJavaScriptです。let count: number = 0 のように型注釈を書くと、コンパイラ tsc が型を検査し、ブラウザでもNode.jsでも動く通常のJavaScriptに変換します。基本の型、オブジェクトの形を表すインターフェースと型エイリアス、型の絞り込み(ナローイング)を伴うユニオン型、再利用できるコードのためのジェネリクスが要点です。型は実行時には消えます。

JSONフォーマッタを開く

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には型注釈を付けましょう。

型例備考
numberlet n: number = 3.5整数と浮動小数点数
stringlet s: string = "hi"文字列
booleanlet ok: boolean = truetrue または false
配列number[] または Array<number>どちらの書き方も同じ意味
タプル[string, number]長さと各位置の型が固定
unknownlet x: unknown任意の値。使う前に確認が必要
anylet 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")); // AB7

string | 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と同じ速度で動きます。メリットはバグの減少と、ツールの支援が強化されることです。

コーディング講座の一覧