TypeScript 入門

基礎編

TypeScriptとは?

TypeScript = JavaScript + 静的型システム

  • JavaScriptはTypeScriptの部分集合です
  • 実行にはJavaScriptへのトランスパイルが必要
  • Microsoftが2012年に開発
  • 現在の最新バージョンは 5.8.3
  • TypeScript自体もTypeScriptで書かれている
TypeScript 7 は、パフォーマンス向上のために Go で書き直される予定です。

TypeScriptのライフサイクル

.ts ファイル

TypeScriptコード

tsc

型チェック & 変換

.js ファイル

実行可能なJS

重要: TypeScriptは実行時に存在しない。型情報はコンパイル時のみで使用される。

コード例(変換前後)

TypeScript

                                
    // hello.ts
    function greet(name: string): string {
        return `Hello, ${name}!`;
    }
                                
                            

JavaScript

                                
    // hello.js(コンパイル後)
    function greet(name) {
        return `Hello, ${name}!`;
    }
                                
                            

型だけでDoom

TypeScriptの型システムはチューリング完全であり、型だけで Doomを再現可能。

このプロジェクトでは3.5兆行の型定義と177TBのデータを処理し、1フレームの描画に12日間かかりました。

なぜ2025年にTypeScript?

1. 型安全性 - バグの早期発見

JavaScript - 実行時エラー

function getUser(id) {
  return users.find(u => u.id === id);
}

const user = getUser("123");
console.log(user.name.toUpperCase());
// ☠️ TypeError: Cannot read property 'name' of undefined

TypeScript - コンパイル時に防げる

function getUser(id: string): User | undefined {
  return users.find(u => u.id === id);
}

const user = getUser("123");
console.log(user.name.toUpperCase());
// ❌ Error: Object is possibly 'undefined'

2. チーム開発に強い

  • 共通理解: 型定義がAPIの仕様書になる
  • 安全なリファクタ: 型が変更の影響を可視化
  • IDE支援: 補完、警告、ジャンプ機能など

3. ドキュメントと型の一体化

  • ドリフト防止: コードと仕様が常に一致
  • 自動生成: 型とコメントからAPI仕様を生成
  • 型ベースの検証: zodなどでランタイムにも活用可

4. なめらかな開発体験

  • 初期設計から型を定義すると堅牢なコードに導かれる
  • コードレビューが効率化される
  • バックエンドと契約駆動開発しやすい

5. AIとコード

  • 型 = ヒント: LLMが正確に意図を読み取れる
  • 非型言語では: 推測ベースで不安定な提案が多い
  • 型情報の活用: 設計レベルの支援も可能になる

TypeScriptのセットアップ

導入方法、コンパイル方法、tsconfig.jsonの設定、.d.tsファイル

TypeScriptの導入方法

  • npm でインストール (グローバル or ローカル)
  • 最近のエディターには組み込みサポート

npmでのインストール

# グローバル (どこでもCLI使用可能)
npm install -g typescript

# ローカル (プロジェクト推奨)
npm install --save-dev typescript

基本的なコンパイル

# 全てをコンパイル
>>> tsc

# 型チェックのみ (JSファイル出力なし)
>>> tsc --noEmit

# 監視モード (変更時に自動再コンパイル)
>>> tsc --watch

tsconfig.json って何?

  • TypeScriptにコンパイル方法を指示
  • プロジェクトの境界を定義 (どのファイルを含むか)
  • 型チェックルールを設定
Tip: tsc --init で基本的なtsconfig.jsonを自動生成できる

tsconfig.json の基本設定

{
  "compilerOptions": {
    "target": "ES2020",             // 出力されるJavaScriptのバージョン
    "module": "ESNext",             // 使用するモジュール方式
    "outDir": "./dist",             // コンパイル後のJavaScriptファイルの出力先ディレクトリ
    "rootDir": "./src",             // ソースのTypeScriptファイルがあるルートディレクトリ
    "strict": true,                 // 厳格な型チェックをすべて有効にする(推奨設定)
    "esModuleInterop": true         // CommonJSのモジュールをESモジュールのようにimport可能にする
  },
  "include": ["src/**/*"],          // コンパイル対象に含めるファイル
  "exclude": ["node_modules"]       // コンパイルから除外するファイルやフォルダ
}

d.ts ファイルとは?

  • 型定義のみを記述するファイル
  • JSライブラリにTypeScriptの型情報を追加
  • 実際のコードは含まない
// example.d.ts - 型定義ファイル
declare function myLibFunction(x: string): number;
declare const VERSION: string;

// モジュールの型定義
declare module "some-js-library" {
  export function doSomething(data: any): Promise;
}
実例: npm install @types/node でNode.jsのAPI型定義を取得

基本の型

TypeScriptの型システムの基礎

プリミティブ型、配列、オブジェクト、anyunknown

プリミティブ型

// 基本的なプリミティブ型
let characterName: string = "古手梨花";
let age: number = 11;
let isAlive: boolean = true;
let deathCount: null = null;
let mysteryStatus: undefined = undefined;

// symbol と bigint (ES2015+)
let fragmentId: symbol = Symbol("fragment");
let loopCount: bigint = 100000000000000000000n;
文字列、数値、真偽値、nullundefinedsymbolbigint

配列型

// 配列の型定義 - 2つの書き方
let clubGrades: number[] = [1, 2, 3, 2, 1];
let clubMembers: Array<string> = ["竜宮レナ", "園崎魅音", "古手梨花", "北条沙都子"];

// 読み取り専用配列
let rules: ReadonlyArray<string> = ["鬼隠し", "綿流し", "祟殺し"];
// rules.push("暇潰し"); // エラー!

// 読み取り専用配列(readonly 修飾子)
let legends: readonly string[] = ["雛見沢症候群", "綿流しの儀式", "祟りの噂"];
// legends.pop(); // エラー!
Type[] vs Array<Type> - どちらでもOK、チーム規約で統一

配列型

// 混合型の配列
let evidence: (string | number)[] = ["鬼隠し", 1983, "綿流し", 1982];

// 多次元配列
let board: string[][] = [
  ["X", "O", "X"],
  [" ", "X", "O"],
  ["O", " ", " "]
];

オブジェクト型

// オブジェクトの型定義
let clubMember: { name: string; age: number; weapon?: string } = {
    name: "竜宮レナ",
    age: 16
};

// インデックスシグネチャ
let suspicionLevels: { [character: string]: number } = {
    "前原圭一": 3,
    "竜宮レナ": 5,
    "園崎魅音": 2
};

オブジェクト型

// ネストしたオブジェクト
let village: {
    name: string;
    location: {
        prefecture: string;
        population: number;
    };
} = {
    name: "雛見沢村",
    location: {
        prefecture: "岐阜県",
        population: 2000
    }
};

オプショナルプロパティ

?マークは「省略可能」を意味し。weaponは省略可能。

let character: { 
    name: string; 
    weapon?: string; // string | undefined
} = {
    name: "古手梨花"
};

if (character.weapon) { 
    console.log(character.weapon.toUpperCase()); // 安全なアクセス方法:型ガード
}

console.log(character.weapon?.toUpperCase()); // 安全なアクセス方法:オプショナルチェーン

undefinedとの違い

let explicitUndefined: { name: string; weapon: string | undefined } = {
    name: "前原圭一",
    weapon: undefined  // 明示的にundefinedを設定する必要がある
};

any型

// any型 - TypeScriptの型チェックを無効化
let mysteryData: any = "綿流し";
mysteryData = 1983;
mysteryData = true;
mysteryData = { killer: "unknown" };

// なんでもできてしまう(危険)
mysteryData.oyashiro.curse.activate; // エラーにならない
mysteryData(); // エラーにならない
mysteryData[0][1][2]; // エラーにならない

// 型の利益を失う
let result = mysteryData + 5; // resultの型もany
anyはTypeScriptの型安全性を完全に無効化する

any型の問題

  • 型チェックが無効化される
  • コード補完が効かない
  • ランタイムエラーの原因
  • リファクタリングが困難
  • TypeScriptの恩恵を失う

unknown型

let secret: unknown = investigate();

// 直接操作はエラー
// villageSecret.toUpperCase(); // エラー!
// villageSecret + 5; // エラー!

// 型ガードが必要
if (typeof secret === "string") {
  console.log(secret.toUpperCase()); // OK
}

if (typeof secret === "number") {
  console.log(secret.toFixed(2)); // OK
}
unknownは使用前に型チェックを強制する

理解度チェック①

どちらがエラーになる?

let mysteryA: any = "梨花";
let mysteryB: unknown = "梨花";

mysteryA.toUpperCase(); // A
mysteryB.toUpperCase(); // B
A) mysteryA.toUpperCase()
B) mysteryB.toUpperCase()
C) 両方エラー
D) 両方エラーなし
答え: B) unknownは型ガードが必要。anyは何でもOK(危険)

理解度チェック②

次のコードで型エラーになるのは?

let clubMembers: string[] = ["圭一", "レナ", "魅音"];
let suspicionLevels: number[] = [3, 5, 2];

clubMembers.push("梨花"); // A
clubMembers.push(4); // B
suspicionLevels[0] = 10; // C
console.log(suspicionLevels.length); // D
A) clubMembers.push("梨花");
B) clubMembers.push(4);
C) suspicionLevels[0] = 10;
D) suspicionLevels.length;
答え: B) string[]numberは追加できない

理解度チェック③

どの代入が可能?

let villager: { name: string; age: number; occupation: string } = {
  name: "入江京介",
  age: 35,
  occupation: "医師",
};

villager.name = "富竹ジロウ"; // A
villager.email = "irie@clinic.com"; // B
villager.age = "三十五歳"; // C
villager = null; // D
A) villager.name = "富竹ジロウ";
B) villager.email = "irie@clinic.com";
C) villager.age = "三十五歳";
D) villager = null;
答え: A) のみ。存在するstring型プロパティへの代入

理解度チェック④

どの操作がエラーになる?

let gameArcs: ReadonlyArray<string> = ["鬼隠し", "綿流し", "祟殺し"];

console.log(gameArcs[0]) // A 
console.log(gameArcs.length) // B 
gameArcs.push("暇潰し") // C 
let copy = [...gameArcs] // D 
A) gameArcs[0]
B) gameArcs.length
C) gameArcs.push("暇潰し")
D) [...gameArcs]
答え: C) ReadonlyArrayは変更操作が禁止

型推論 「Type Inference」

// 基本的な型推論
let price = 1000;        // number
let name = "商品A";      // string
let available = true;    // boolean

// 配列推論
let items = [1, 2, 3];   // number[]
let mixed = [1, "text"]; // (number | string)[]
ベストプラクティス: 明示的な型注釈は避けて推論に任せる

d.tsファイルでの型情報

// 元のコード
export function processOrder(id: number) {
    const order = { id, status: "pending", total: 0 };
    return order;
}

// 生成されるd.ts
export declare function processOrder(id: number): {
    id: number;
    status: string;
    total: number;
};
Tip: 関数の戻り値は基本的に型推論に任せる。パラメータや複雑な型だけ明示する。

Union型

複数の型を許可する柔軟性

「AまたはB」の型を表現

| (pipe) 演算子で結合

基本的なUnion型

// 数値または文字列
let userId: number | string = 123;
userId = "user_456"; // 再代入OK

// 複数の型を許可
let value: string | number | boolean;
value = "hello";  // ✓
value = 42;       // ✓
value = true;     // ✓
value = null;     // ❌ Error
型安全性を保ちながら柔軟性を確保

型ガードでの絞り込み

function formatId(id: number | string): string {
    if (typeof id === "number") {
        // この中ではnumber型として扱われる
        return `ID: ${id.toString().padStart(6, '0')}`;
    } else {
        // この中ではstring型として扱われる
        return `ID: ${id.toUpperCase()}`;
    }
}

formatId(123);        // "ID: 000123"
formatId("abc_456");  // "ID: ABC_456"
型ガード: 実行時チェックでTypeScriptが型を理解

null許可型

// よく使うパターン
let message: string | null = null;
let data: User | undefined;

// null チェック後は型が絞り込まれる
if (message !== null) {
    console.log(message.toUpperCase()); // string確定
}

// Optional chaining と組み合わせ
function processUser(user: User | null) {
    console.log(user?.name?.toUpperCase());
}
strictNullChecksが有効なら必須の考慮

リテラル型のUnion

// enum的な使い方
type Status = "pending" | "approved" | "rejected";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

function makeRequest(url: string, method: HttpMethod) {
    // 実装
}

makeRequest("/api/users", "GET");    // ✓ Valid
makeRequest("/api/users", "PATCH");  // ❌ Error

// 数値リテラルも可能
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
文字列定数の集合を型として表現

判別可能なUnion「Discriminated Union」

// 共通プロパティで判別
type ApiResponse = 
    | { success: true; data: any }
    | { success: false; error: string };

function handleResponse(response: ApiResponse) {
    if (response.success) {
        console.log(response.data);    // dataが存在することを理解
    } else {
        console.log(response.error);   // errorが存在することを理解
    }
}
判別プロパティ: successで型を特定

理解度チェック⑤

どの代入がエラーになる?

type GameResult = "win" | "lose" | "draw";
let result: GameResult;

result = "win";     // A
result = "victory"; // B
result = "draw";    // C
result = null;      // D
A) result = "win"
B) result = "victory"
C) result = "draw"
D) result = null
答え: B, D - 定義された文字列リテラル以外はエラー

関数の型定義

// 基本的な関数
function add(a: number, b: number): number {
    return a + b;
}

// アロー関数
const multiply = (x: number, y: number): number => x * y;
// コールバック関数の型
function processData(
    data: string[], 
    callback: (item: string) => string
): string[] {
    return data.map(callback);
}

関数の型定義

// オプショナル引数(?を使う)
function greet(name: string, title?: string): string {
    if (title) {
        return `${title} ${name}`;
    }
    return `Hello, ${name}`;
}
// デフォルト引数
function createUser(name: string, age: number = 18) {
    return { name, age }; // 戻り値の型は推論される
}
// Rest引数
function sum(...numbers: number[]): number {
    return numbers.reduce((total, num) => total + num, 0);
}

関数オーバーロード

function convert(input: string): number;
function convert(input: number): string;
function convert(input: string | number): string | number {
    if (typeof input === "string") {
        return parseInt(input, 10);
    } else {
        return input.toString();
    }
}
オーバーロードは関数の異なるシグネチャを定義することで、同じ関数名で異なる引数を受け取ることができる

void型とnever型

特殊な戻り値の型

void: 戻り値なし(undefined)

never: 絶対に戻らない

void型 - 戻り値なし

// void型 - undefinedを返す
function logMessage(message: string): void {
    console.log(`[LOG] ${message}`); // 暗黙的にundefinedを返す
}
// voidは通常推論されるので省略可能
function saveToDatabase(data: any) {
    console.log("データを保存しました"); // 戻り値の型は自動的にvoidになる
}
// void関数からundefinedは返せる
function processData(): void {
    console.log("処理完了");
    return; // OK
    // return undefined; // これもOK
    // return null; // ❌ Error
}
用途: 副作用のある関数(ログ出力、DOM操作など)

never型 - 絶対に戻らない

function throwError(message: string): never { // never型 - 例外を投げる
    throw new Error(message); // この後のコードは実行されない
}

function infiniteLoop(): never { // never型 - 無限ループ
    while (true) {}
}

function processValue(value: unknown): string { // 関数が途中で例外を投げる場合
    if (typeof value === "string") {
        return value.toUpperCase();
    }
    throwError("文字列以外は処理できません"); // ここでnever型を返す
}
用途: エラーハンドリング、到達不可能コードの表現

never型の実用例

網羅性チェック 「Exhaustive Checking」

type Color = "amethyst" | "green";

function getColorHex(color: Color): string {
    switch (color) {
        case "amethyst":
            return "#955CD0";
        case "green":
            return "#8EDF5F";
        default:
            // 新しい色をUnionに追加したらここでエラー
            const exhaustiveCheck: never = color;
            throw new Error(`未処理の色: ${exhaustiveCheck}`);
    }
}
メリット: Union型に新しい値を追加した時、処理漏れを防げる

void vs never の違い

void型

  • 関数は正常終了する
  • undefinedを返す
  • 副作用のある処理
  • 呼び出し元に制御が戻る

never型

  • 関数は絶対に終了しない
  • 何も返さない
  • 例外・無限ループ
  • 呼び出し元に制御が戻らない

理解度チェック⑥

どの関数がnever型を返すべき?

// A
function printHello() {
    console.log("Hello!");
}

// B
function divide(a: number, b: number) {
    if (b === 0) {
        throw new Error("ゼロ除算エラー");
    }
    return a / b;
}
// C
function panic() {
    throw new Error("システムエラー");
}

// D
function getUserName() {
    return null;
}
A) printHello
B) divide
C) panic
D) getUserName
答え: C) panic - 常に例外を投げて絶対に戻らない

TypeScript 実践演習

3つの課題 (各5-10分)

今日学んだ内容を実際に使ってみよう!

TypeScript Playground

Playgroundでできること:

  • ブラウザでTypeScriptを実行・テスト
  • コンパイル結果をリアルタイム確認
  • 型エラーの詳細表示
  • 異なるターゲット(ES5, ES2020等)の比較
  • 型定義の探索とテスト

課題1 Union型とtype guard

関数 processId を作成してください

  • 引数 idnumber または string のいずれかです
  • number の場合は "ID: 123"string の場合は "CODE: ABC" の形式で返す
// テスト
console.log(processId(123));    // "ID: 123"
console.log(processId("ABC"));  // "CODE: ABC"
                

課題1 解答例

型ガード 【type guard】を使うことができます

function processId(id: number | string): string {
    // typeof演算子を使って型を判定
    if (typeof id === "number") {
        return `ID: ${id}`;
    } else {
        return `CODE: ${id}`;
    }
}

課題2 オプション値と配列

関数 createMessage を作成してください

  • 名前と省略可能なタグを受け取ります
  • タグがある場合は "Hello 名前 [tag1, tag2]"、ない場合は "Hello 名前" を返します
// テスト
console.log(createMessage("Takano"));                    // "Hello Takano"
console.log(createMessage("Satoko", ["admin", "user"])); // "Hello Satoko [admin, user]"
                

課題2 解答例

オプション引数は undefined かもしれないので存在チェックが必要

function createMessage(name: string, tags?: string[]): string {
    if (tags) { // tags?.join() でもOK
        return `Hello ${name} [${tags.join(", ")}]`;
    } else {
        return `Hello ${name}`;
    }
}

課題3 型推論

関数 investigateFragment を作成してください

  • 引数: fragments は数値の配列または文字列の配列
  • 配列の最初の要素を返す。空配列の場合は "nanodesu" を返す
  • 型を判定する処理を書いてください
// テスト
console.log(investigateFragment([1983, 1984, 1985]));   // 1983
console.log(investigateFragment(["rena", "mion"]));     // "rena"
console.log(investigateFragment([]));                   // "nanodesu"
    

課題3 解答例

空配列の場合の処理がポイント

function investigateFragment(fragments: number[] | string[]): number | string | "nanodesu" {
    // 空配列の場合の処理
    if (fragments.length === 0) {
        return "nanodesu";
    }
    
    // 型ガードで配列の型を判定
    if (typeof fragments[0] === "number") {
        return fragments[0]; // number配列の場合
    } else {
        return fragments[0]; // string配列の場合
    }
}

課題3 別解

TypeScriptの型推論を活用した書き方

// 別解1: 型推論を活用
function investigateFragment(fragments: number[] | string[]): number | string | "nanodesu" {
    if (fragments.length === 0) {
        return "nanodesu";
    }
    
    return fragments[0]; // TypeScriptが自動で型を推論
}

// 別解2: より簡潔な書き方
function investigateFragment(fragments: number[] | string[]): number | string | "nanodesu" {
    return fragments[0] ?? "nanodesu";
}

第1回まとめ

今日学んだこと

  • TypeScript の基本: tsc コンパイラーと型システム
  • コマンドライン操作: プロジェクト設定と tsconfig.json
  • 基本型: primitive types、arrays、objects
  • 型推論: TypeScript の自動型判定システム
  • Union型: 複数の型を許可する方法
  • 関数の型: 引数、戻り値、オプション、オーバーロード
  • void と never: 特殊な戻り値型

宿題・実践課題

斬魄刀コレクション管理システム

実践での重要ポイント

  • 型推論を信頼しつつ、関数の戻り値は明示的に書く
  • Union型は実際のデータ構造を反映する("melee" | "kido" など)
  • Optional型(?)で「あるかもしれない」データを表現
  • Type guardでランタイムの型チェックと型の絞り込みを両立
  • 配列操作(filter, map, sort)とTypeScriptの組み合わせに慣れる

次回予告

TypeScript入門②: interface、type aliases、enum、generics、utility types