Skip to content

TypeScript ディープガイド

はじめに

あなたはもう JavaScript を書けますが、おそらくこんな問題に遭遇したことがあるでしょう:

  • 変数に誤った型を代入して、実行時になって初めて気づく
  • オブジェクトのプロパティ名を間違えて、デバッグに時間がかかる
  • 関数の引数の型が合わず、何度も修正する

TypeScript は、コードを実行する前にこれらの問題を発見してくれるツールです。この記事を読み終えれば、TypeScript がなぜコード品質を向上させるのかを理解し、型アノテーション、インターフェース、ジェネリクスなどのコアコンセプトを読み解き、vibecoding で AI が生成したコードをより活用できるようになります。

この記事で学べること:

内容学んだ後にできること
第 1 章TypeScript とはJavaScript との関係がわかる
第 2 章基本の型アノテーション変数に型を付ける方法がわかる
第 3 章オブジェクト型とインターフェースデータ構造の型を定義できる
第 4 章関数の型関数の引数と戻り値に型を付けられる
第 5 章ジェネリクス再利用可能な型安全なコードを書ける
第 6 章型推論と実用テクニックいつ明示的なアノテーションが必要かわかる

1. TypeScript とは

🤔 核心的な問い

JavaScript で十分なのに、なぜ TypeScript が必要なのか? 新しい文法を学ぶ価値はあるのか?

1.1 「実行時エラー」から「コンパイル時発見」へ

🔴 JavaScript の痛点

  • 実行時まで型エラーに気づけない
  • スペルミスを見つけにくい
  • リファクタリング時に見落としが発生しやすい
  • IDE の補完が不正確になりがち

スペルチェックのない文書エディタのようなもの

✅ TypeScript の利点

  • コードを書いている段階でエラーを発見
  • インテリセンスがより正確に
  • リファクタリングがより安全に
  • コードの保守性が向上

スペルチェックとシンタックスハイライト付きエディタのようなもの

両者の関係を一言で理解する:

技術たとえ役割
JavaScript原材料そのまま実行できるコード
TypeScript設計図 + 品質検査JavaScript に型チェックを追加し、最終的に JavaScript にコンパイルされる

1.2 vibecoding にも TypeScript が必要な理由

AI が書くコードも間違える

ある開発者が AI を使ってユーザー管理機能を生成しました。AI が書いた JavaScript コードは動きましたが、問題がありました。ユーザーの年齢は数値であるべきなのに、時々文字列として誤って代入されていたのです。

その結果、「成人かどうか」を判定する際に、文字列 "25" が文字列として処理され、判定が失敗しました。このバグは、あるユーザーが数字以外の文字を入力するまで長い間隠れていました。

もし TypeScript を使っていれば、このコードは書いた時点でエラーになります:型 'string' を型 'number' に割り当てることはできません

これが TypeScript の価値です——AI が型を間違えたとき、あなたが最初に気づけるのです。

1.3 TypeScript の実態はこうだ

TypeScript はまったく新しい言語ではなく、JavaScript の「スーパーセット」です:

typescript
// これは有効な JavaScript であり、有効な TypeScript でもある
const name = "張三"
const age = 25
function greet(user) {
  return `Hello ${user}`
}

// これは TypeScript 特有の型アノテーション
const name2: string = "李四"
const age2: number = 30
function greet2(user: string): string {
  return `Hello ${user}`
}

重要な理解:

  • すべての JavaScript コードは有効な TypeScript コードである
  • TypeScript はオプショナルな型アノテーションを追加する
  • TypeScript は最終的に JavaScript にコンパイルされて実行される

💡 核心的な気づき

TypeScript はコードの実行方法を変えるのではなく、コンパイル時に型が正しいかチェックするだけです。段階的に TypeScript を導入できます——重要な変数に型を付けるところから始めましょう。


2. 基本の型アノテーション

🤔 核心的な問い

変数がどの型であるべきかを TypeScript にどう伝えるか? 型アノテーションの文法は?

2.1 型アノテーションの文法

型アノテーションは変数名の後に : 型 を付けます:

typescript
// 文法:変数名: 型 = 値
const name: string = "張三"
let age: number = 25
let isStudent: boolean = true

👇 実際に試してみよう:変数に型アノテーションを追加する

📝 TypeScript Type Annotation Demo

stringname
Alice
const name: string = "Alice"
numberage
25
const age: number = 25
booleanisActive
true
const isActive: boolean = true
JavaScript (no type checking)
let name = "Alice"
name = 123  // Runtime error may appear much later
TypeScript (compile-time checking)
let name: string = "Alice"
name = 123  // Compile-time error appears immediately
🔍 なぜ型アノテーションが不要な場合があるのか?

TypeScript は代入から自動的に型を推論できます:

typescript
// これらは型アノテーション不要、TypeScript が自動推論する
const name = "張三"      // string と推論
const age = 25          // number と推論
const isActive = true   // boolean と推論

// これらのケースでは明示的なアノテーションが必要
let data  // ❌ エラー:型を推論できない
let data: any  // ✅ 可能だが、型チェックの恩恵を失う

function add(a, b) {  // ❌ 引数の型が不明確
  return a + b
}

function add2(a: number, b: number): number {  // ✅ 型が明確
  return a + b
}

2.2 基本型

TypeScript は JavaScript のすべての基本型をサポートしています:

説明
string文字列"hello", 'こんにちは'
number数値(整数と小数)42, 3.14
boolean真偽値true, false
null / undefined空値null, undefined
array配列number[], string[]
objectオブジェクト{ name: string; age: number }

配列型の 2 つの書き方:

typescript
// 書き方 1:型[](より一般的)
const numbers: number[] = [1, 2, 3, 4, 5]
const names: string[] = ["張三", "李四", "王五"]

// 書き方 2:Array<型>
const numbers2: Array<number> = [1, 2, 3, 4, 5]
const names2: Array<string> = ["張三", "李四", "王五"]

特殊な型:

typescript
// any:任意の型(慎重に使用、型チェックを無効にするのに等しい)
let data: any = 42
data = "文字列に変更可能"
data = { name: "張三" }  // オブジェクトにもできる

// unknown:型安全な any
let value: unknown = 42
// if (typeof value === "number") {
//   console.log(value + 10)  // 型チェックをしてから使う必要がある
// }

// void:戻り値なし
function log(message: string): void {
  console.log(message)
}

// never:決して戻らない
function error(message: string): never {
  throw new Error(message)
}

💡 見分け方

  • : string を見たら → これは string 型のアノテーション
  • : number[] を見たら → これは数値配列のアノテーション
  • : void を見たら → この関数は戻り値がない

3. オブジェクト型とインターフェース

🤔 核心的な問い

オブジェクトの型をどう定義するか? オブジェクトのプロパティはどの型であるべきか?

3.1 インターフェース(Interface):オブジェクトの「形状」を定義する

インターフェースは TypeScript でオブジェクトの型を定義する主要な方法です:

typescript
// User インターフェースを定義
interface User {
  id: number
  name: string
  email: string
  age?: number  // オプショナルプロパティ
}

// インターフェースを使用
const user: User = {
  id: 1,
  name: "張三",
  email: "zhangsan@example.com",
  age: 25
}

// age はオプショナルなので、省略可能
const user2: User = {
  id: 2,
  name: "李四",
  email: "lisi@example.com"
}

👇 実際に試してみよう:インターフェース定義に合ったオブジェクトを作成する

🎯 Interface Demo

User Interface Definition
interface User {
  id: number
  name: string
  email: string
  age: number
}
👤
ID:1number
Age:25number
✅ Correct usage
const user: User = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  age: 25
} // fully matches the type
❌ Incorrect usage
const user: User = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  age: "25"  // error: age should be number, not string
}
🔍 インターフェースのその他の機能
typescript
// 読み取り専用プロパティ
interface User {
  readonly id: number  // id は作成後に変更不可
  name: string
}

const user: User = {
  id: 1,
  name: "張三"
}

user.id = 2  // ❌ エラー:読み取り専用プロパティは変更不可
user.name = "李四"  // ✅ 変更可能

// 関数型
interface User {
  name: string
  greet: () => string  // greet は関数で、string を返す
}

const user: User = {
  name: "張三",
  greet: () => "Hello"
}

// インターフェースの継承
interface Admin extends User {
  permissions: string[]
}

const admin: Admin = {
  name: "管理者",
  greet: () => "Hello Admin",
  permissions: ["read", "write", "delete"]
}

3.2 型エイリアス(Type Alias)

インターフェースの他に、type で型エイリアスを定義することもできます:

typescript
// 型エイリアス
type User = {
  id: number
  name: string
  email: string
}

// ユニオン型
type Status = "pending" | "success" | "error"

const status: Status = "success"  // ✅
// const status2: Status = "failed"  // ❌ エラー:ユニオン型に含まれていない

// 交差型(複数の型をマージ)
type User = {
  id: number
  name: string
}

type Timestamp = {
  createdAt: Date
  updatedAt: Date
}

type UserWithTimestamp = User & Timestamp

const user: UserWithTimestamp = {
  id: 1,
  name: "張三",
  createdAt: new Date(),
  updatedAt: new Date()
}

インターフェース vs 型エイリアス:

特性interfacetype
拡張extends& 交差型
重複宣言自動的にマージされるエラーになる
適したシーンオブジェクトの形状、クラスユニオン型、交差型、基本型のエイリアス

💡 見分け方

  • interface を見たら → オブジェクト型の定義
  • type を見たら → 型エイリアスの作成
  • ? を見たら → オプショナルプロパティ
  • readonly を見たら → 読み取り専用プロパティ

4. 関数の型

🤔 核心的な問い

関数の引数と戻り値にどう型を付けるか?

4.1 引数の型と戻り値の型

typescript
// 完全な関数の型アノテーション
function add(a: number, b: number): number {
  return a + b
}

// アロー関数
const multiply = (a: number, b: number): number => {
  return a * b
}

// 戻り値がない
function log(message: string): void {
  console.log(message)
}

// 複数の型を返す(ユニオン型)
function parseInput(input: string): number | string {
  const num = parseFloat(input)
  return isNaN(num) ? input : num
}

4.2 オプショナルパラメータとデフォルトパラメータ

typescript
// オプショナルパラメータ(? でマーク)
function greet(name: string, title?: string): string {
  return title ? `${title} ${name}` : name
}

greet("張三")  // "張三"
greet("張三", "様")  // "様 張三"

// デフォルトパラメータ
function greet2(name: string, title: string = "友達"): string {
  return `${title} ${name}`
}

greet2("李四")  // "友達 李四"
greet2("李四", "博士")  // "博士 李四"

4.3 関数型を引数として

typescript
// 関数を引数として受け取る
function calculate(
  a: number,
  b: number,
  operation: (x: number, y: number) => number
): number {
  return operation(a, b)
}

calculate(10, 5, (x, y) => x + y)  // 15
calculate(10, 5, (x, y) => x * y)  // 50

// より明確な書き方:先に関数型を定義
type Operation = (x: number, y: number) => number

function calculate2(
  a: number,
  b: number,
  operation: Operation
): number {
  return operation(a, b)
}

💡 見分け方

  • (a: number, b: number) => number を見たら → 関数型で、引数と戻り値を記述している
  • : void を見たら → 関数に戻り値がない
  • ? を見たら → 引数がオプショナル

5. ジェネリクス

🤔 核心的な問い

複数の型を扱えて、かつ型安全なコードをどう書くか?

5.1 ジェネリクスの基本概念

ジェネリクスを使うと、関数やインターフェース、クラスを定義する際に具体的な型を事前に指定せず、使用時に指定できます:

typescript
// ジェネリクス関数:T は型変数
function identity<T>(arg: T): T {
  return arg
}

// 使用時に明示的に型を指定
const num1 = identity<number>(42)  // 型は number
const str1 = identity<string>("hello")  // 型は string

// 型推論:TypeScript が自動推論できる
const num2 = identity(42)  // number と推論
const str2 = identity("hello")  // string と推論

👇 実際に試してみよう:ジェネリクスを使って異なる型のデータを処理する

🔄 Generics Demo

💡
Generics are reusable templates - They handle different data types while preserving type safety
Generic Function Definition
// T is a type variable decided when the function is used
function identity<T>(arg: T): T {
  return arg
}

// Generic array reversal
function reverseArray<T>(arr: T[]): T[] {
  return [...arr].reverse()
}

📝 Generic Usage Examples

Number array
const nums = [1, 2, 3, 4, 5]
const reversed = reverseArray<number>(nums)
// result: [5, 4, 3, 2, 1]
// type: number[]
String array
const strs = ["a", "b", "c"]
const reversed = reverseArray<string>(strs)
// result: ["c", "b", "a"]
// type: string[]

5.2 ジェネリクス制約

ジェネリクスが特定の条件を満たすことを制限する:

typescript
// T が length プロパティを持つことを制約
interface HasLength {
  length: number
}

function logLength<T extends HasLength>(arg: T): void {
  console.log(arg.length)
}

logLength("hello")  // ✅ 文字列は length を持つ
logLength([1, 2, 3])  // ✅ 配列は length を持つ
// logLength(42)  // ❌ 数値は length プロパティを持たない

5.3 ジェネリクスインターフェースとクラス

typescript
// ジェネリクスインターフェース
interface Box<T> {
  value: T
  getValue(): T
}

const numberBox: Box<number> = {
  value: 42,
  getValue: () => 42
}

const stringBox: Box<string> = {
  value: "hello",
  getValue: () => "hello"
}

// ジェネリクスクラス
class Storage<T> {
  private items: T[] = []

  add(item: T): void {
    this.items.push(item)
  }

  get(index: number): T {
    return this.items[index]
  }
}

const numberStorage = new Storage<number>()
numberStorage.add(1)
numberStorage.add(2)
// numberStorage.add("string")  // ❌ エラー

const stringStorage = new Storage<string>()
stringStorage.add("hello")
// stringStorage.add(1)  // ❌ エラー

💡 見分け方

  • <T> を見たら → ジェネリクス型変数
  • <T extends SomeType> を見たら → ジェネリクス制約
  • Array<T>Promise<T> を見たら → 組み込みのジェネリクス型

6. 型推論と実用テクニック

🤔 核心的な問い

いつ明示的な型アノテーションが必要で、いつ推論に任せられるか?

6.1 型推論

TypeScript は文脈から自動的に型を推論できます:

typescript
// 変数初期化時の推論
const name = "張三"  // string と推論
const age = 25  // number と推論
const isActive = true  // boolean と推論

// 配列の推論
const numbers = [1, 2, 3]  // number[] と推論
const mixed = [1, "hello", true]  // (number | string | boolean)[] と推論

// 関数の戻り値の推論
function add(a: number, b: number) {
  return a + b  // 戻り値は number と推論
}

👇 実際に試してみよう:TypeScript がどのように型を推論するか観察する

🔮 Type Inference Demo

🧠

What is type inference?

TypeScript can infer variable types from your code, so you do not need to annotate every value manually.

Choose an example to see how type inference works:

let name = "Alice"
→ string
let age = 25
→ number
let isActive = true
→ boolean
let numbers = [1, 2, 3]
→ number[]
💻Code
let name = "Alice"
🏷️Inferred type
string
💡
TypeScript infers that name has type string from the assigned string value.

📚 Best Practices

When to use inference
  • A variable has a clear initial value
  • The function return type is obvious
  • The assignment is a simple literal
When to add annotations
  • Function parameters are required
  • Objects or arrays have complex structure
  • The initial value cannot reveal the type
  • You need an explicit type constraint

🔄 Type Inference vs Explicit Annotation

Function return value
Use inference
function add(a: number, b: number) {
  return a + b  // inferred as number
}
Explicit annotation
function add(a: number, b: number): number {
  return a + b
}
Inference is recommended
Complex object
Use inference
const user = {
  name: "Alice",
  age: 25,
  email: "test@example.com"
}  // type is inferred automatically
Explicit annotation
interface User {
  name: string
  age: number
  email: string
}

const user: User = { ... }
Use an interface for complex structures

6.2 明示的な型アノテーションを使うべきタイミング

型推論の使用を推奨するシーン
typescript
// ✅ 推奨:単純なリテラル代入
const count = 0
const name = "張三"
const isActive = true

// ✅ 推奨:関数の戻り値が推論可能
function getUserId(user: User) {
  return user.id  // number と推論
}
明示的なアノテーションを推奨するシーン
typescript
// ✅ 推奨:関数の引数(必須)
function add(a: number, b: number) {
  return a + b
}

// ✅ 推奨:オブジェクトプロパティの型が不明確
const user: {
  id: number
  name: string
  metadata: Record<string, any>
} = {
  id: 1,
  name: "張三",
  metadata: {}  // {} と推論される可能性があるため、明示が必要
}

// ✅ 推奨:関数の戻り値の型が複雑
function getUser(): User | null {
  // ...
  return null
}

// ✅ 推奨:公開 API
export function calculateTotal(prices: number[]): number {
  return prices.reduce((sum, price) => sum + price, 0)
}

6.3 型ガード

実行時に型をチェックする:

typescript
// typeof 型ガード
function processValue(value: string | number) {
  if (typeof value === "string") {
    // ここでは TypeScript は value が string だとわかっている
    console.log(value.toUpperCase())
  } else {
    // ここでは TypeScript は value が number だとわかっている
    console.log(value * 2)
  }
}

// instanceof 型ガード
class Dog {
  bark() {
    console.log("ワンワン")
  }
}

class Cat {
  meow() {
    console.log("ニャーニャー")
  }
}

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark()  // TypeScript はこれが Dog だとわかっている
  } else {
    animal.meow()  // TypeScript はこれが Cat だとわかっている
  }
}

// カスタム型ガード
interface User {
  name: string
  email: string
}

function isUser(value: any): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    typeof value.name === "string" &&
    typeof value.email === "string"
  )
}

function processValue(value: unknown) {
  if (isUser(value)) {
    // ここでは value は User
    console.log(value.name)
  }
}

6.4 実用的なユーティリティ型

TypeScript は組み込みのユーティリティ型を提供しています:

typescript
// Partial:すべてのプロパティをオプショナルに
interface User {
  id: number
  name: string
  email: string
}

type PartialUser = Partial<User>
// 以下と同等:{ id?: number; name?: string; email?: string }

// Required:すべてのプロパティを必須に
type RequiredUser = Required<PartialUser>
// 以下と同等:{ id: number; name: number; email: string }

// Pick:指定したプロパティのみを残す
type UserBasicInfo = Pick<User, "id" | "name">
// 以下と同等:{ id: number; name: string }

// Omit:指定したプロパティを除外
type UserWithoutEmail = Omit<User, "email">
// 以下と同等:{ id: number; name: string }

// Record:オブジェクト型を作成
type UserRoles = Record<string, boolean>
// 以下と同等:{ [key: string]: boolean }

7. 実践テクニック:vibecoding で TypeScript を使う

🤔 核心的な問い

AI 支援開発で TypeScript をより活用するには?

7.1 AI に型安全なコードを生成させる

❌ 良くないプロンプト:

ユーザー管理機能を書いてください

✅ 良いプロンプト:

ユーザー管理機能を TypeScript で書いてください。

データ構造の定義は以下の通りです:
interface User {
  id: number
  name: string
  email: string
  age: number
}

実装が必要な機能:
1. ユーザー一覧の取得:User[] を返す
2. ユーザーの作成:Partial<User> を受け取り、User を返す
3. ユーザーの更新:id と Partial<User> を受け取り、User を返す
4. ユーザーの削除:id を受け取り、void を返す

すべての関数に完全な型アノテーションを付けてください。

7.2 TypeScript のエラーメッセージを読み解く

よくあるエラーとその意味:

エラーメッセージ意味解決方法
Type 'X' is not assignable to type 'Y'型 X を型 Y に代入できない型が一致するか確認、または型変換を行う
Property 'X' does not exist on type 'Y'型 Y にプロパティ X が存在しないプロパティ名のスペルを確認、またはそのプロパティを定義する
Argument of type 'X' is not assignable to parameter of type 'Y'引数の型が一致しない関数呼び出し時の引数の型を確認
Type 'X' is missing the following properties from type 'Y'型 X に型 Y の特定のプロパティが不足している不足しているプロパティを補完する

7.3 TypeScript の段階的導入

JavaScript プロジェクトがある場合、段階的に TypeScript に移行できます:

  1. ステップ 1:ファイルを .ts にリネーム

    bash
    # utils.js から utils.ts に変更
    mv utils.js utils.ts
  2. ステップ 2:明らかな型エラーを修正

    typescript
    // エラー:Parameter 'a' implicitly has an 'any' type
    // 型アノテーションを追加
    function add(a: number, b: number) {
      return a + b
    }
  3. ステップ 3:段階的に型定義を追加

    typescript
    // まず any で素早く修正
    function processUser(user: any) {
      // ...
    }
    
    // 後で型を充実させる
    interface User {
      id: number
      name: string
    }
    
    function processUser(user: User) {
      // ...
    }
  4. ステップ 4:より厳格な型チェックを有効にする

    json
    // tsconfig.json
    {
      "compilerOptions": {
        "strict": true,  // 厳格モードを有効化
        "noImplicitAny": true,  // 暗黙の any を禁止
        "strictNullChecks": true  // 厳格な null チェック
      }
    }

8. あなたが今識別できるはずのコード

  • : string を見たら → string 型のアノテーション
  • : number[] を見たら → 数値配列のアノテーション
  • interface User を見たら → オブジェクト型の定義
  • type User = を見たら → 型エイリアス
  • <T> を見たら → ジェネリクス
  • extends を見たら → インターフェースの継承またはジェネリクス制約
  • ? を見たら → オプショナルプロパティ
  • readonly を見たら → 読み取り専用プロパティ
  • | を見たら → ユニオン型
  • & を見たら → 交差型

各章の「深掘り」セクションをしっかり読んだなら、以下のコアコンセプトも習得しています:

  • 型アノテーション:変数の型を TypeScript に明示的に伝える
  • インターフェース:オブジェクトの構造と型を定義する
  • ジェネリクス:再利用可能な型安全コードを書く
  • 型推論:TypeScript が自動的に型を推論する
  • 型ガード:実行時に型をチェックする
  • ユーティリティ型:Partial、Required、Pick、Omit など

💡 問題に遭遇したら AI にこう伝えよう

  • 「この関数の型アノテーションはどう書けばいいですか?引数は X で、戻り値は Y です」
  • 「このデータ構造を記述するインターフェースを定義してください:...」
  • 「この TypeScript エラーはどういう意味ですか?どう修正すればいいですか?」
  • 「このジェネリクス関数に制約を追加して、T が特定のプロパティを持つことを保証するには?」