💨

0からはじめるTypeScript型定義マスターへの道

2025/01/19に公開

はじめに

新人教育で質問が多かったので整理しました。

より型安全なコードを書きたい中級者の方も有用な内容かもしれません。よかったら目を通してください。

実践的な例を通じて整理しています。

私的きっちりかっちり理解する利点

開発効率の向上

  • エディタの入力補完が賢くなり、プロパティやメソッドの候補が表示される
  • リファクタリングが安全に行える

チーム開発力向上

  • 型定義がドキュメントとしても機能
  • 意図伝達が容易になる

この記事で整理したこと

  • TypeScriptの基本的な型の使い方
  • 配列とオブジェクトを活用した高度な型定義
  • 実務でよく使用する型定義パターン
  • WebアプリケーションでのTypeScript活用方法

想定読者

  • JavaScriptの基本を理解している方
  • TypeScriptを始めたい方
  • より型安全なコードを書きたい方

1. まずはTypeScriptの型について

TypeScriptを使うとき、「型」というものを指定します。これは「このデータはこういう種類のものですよ」と事前に決めておくようなものです。

基本的な型の例

// 数値の型
let age: number = 25;

// 文字列の型
let name: string = "田中";

// 真偽値(true/false)の型
let isStudent: boolean = true;

これは「年齢は数字」「名前は文字列」「学生かどうかはtrue/false」というように指定しています。

2. 特定の値だけを許可する型を作る

時には「特定の値だけを受け付けたい」という場合があります。

たとえば信号機の色は「赤・青・黄」の3つだけですよね。これを型で表現してみましょう

// 普通の文字列型(なんでもOK)
let anyColor: string = "緑";      // OK
anyColor = "紫";                  // OK
anyColor = "赤";                  // OK

// 信号機の色だけを受け付ける型
type SignalColor = "赤" | "青" | "黄";
let signal: SignalColor = "赤";    // OK
signal = "青";                     // OK
signal = "紫";                     // エラー!

これで「SignalColor型には信号機の色しか入れられない」という制限ができました!

3. 配列の型を考えてみよう

次は配列について見てみましょう。

3.1 普通の配列の場合

// フルーツの配列
const fruits = ["りんご", "バナナ", "オレンジ"];

// この配列は後から変更できます
fruits.push("ぶどう");           // OK
fruits[0] = "メロン";           // OK

// どんな文字列でも追加できてしまいます
fruits.push("パソコン");        // OK(本当はフルーツじゃない...)

3.2 変更できない配列を作る

// as const をつけると変更できない配列になります
const fruits = ["りんご", "バナナ", "オレンジ"] as const;

// 以下はすべてエラーになります
fruits.push("ぶどう");           // エラー!
fruits[0] = "メロン";           // エラー!

as const は「この配列は変更しません!」と約束するようなものです。

4. 配列から型を作る(簡単な例)

では、配列から型を作る方法を見てみます

// フルーツの配列を作る(変更できない配列)
const fruits = ["りんご", "バナナ", "オレンジ"] as const;

// この配列から型を作る
type FruitType = (typeof fruits)[number];
// 結果:この型は "りんご" | "バナナ" | "オレンジ" という
// 3つの値だけを受け付ける型になります

// 使ってみる
let myFruit: FruitType = "りんご";    // OK
myFruit = "バナナ";                   // OK
myFruit = "メロン";                   // エラー!

ここで使った typeof fruits は「fruitsの型を取得する」という意味です。
[number] は「配列の中身の型を取り出す」という意味です。

類似例

以下サンプルでここまでの理解を深めましょう

// 1. まず配列を作る
const days = ["月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日", "日曜日"] as const;

// 2. 型を作る
type DayType = (typeof days)[number];

// 3. 使ってみる
let today: DayType = "月曜日";    // OK
today = "火曜日";                 // OK
today = "明日";                   // エラー!

ここまでのまとめ

  • TypeScriptでは「型」を使ってデータの種類を指定できる
  • as const を使うと変更できない配列を作れる
  • 配列から型を作ることで、決まった値だけを使えるようにできる

では、もう少し複雑な配列やオブジェクトから型を作る方法を見ていきます!

5. オブジェクトについておさらい

まずその前にJavaScriptのオブジェクトについておさらいしましょう。
オブジェクトは、関連するデータをまとめて保存するための入れ物です。

// 学生情報を表すオブジェクト
const student = {
    name: "田中太郎",
    age: 20,
    grade: "A"
};

// データの取り出し方
console.log(student.name);  // "田中太郎"
console.log(student.age);   // 20

6. オブジェクトを変更できないようにする

すでに学んだ as const は、オブジェクトにも使えます

// 普通のオブジェクト(変更可能)
const normalStudent = {
    name: "田中太郎",
    age: 20
};
normalStudent.age = 21;  // OK

// 変更できないオブジェクト
const readonlyStudent = {
    name: "田中太郎",
    age: 20
} as const;
readonlyStudent.age = 21;  // エラー!

7. オブジェクトからキーの型を作る

オブジェクトのプロパティ名(キー)から型を作ることができます。

// 成績情報のオブジェクト
const grades = {
    math: 85,
    english: 90,
    science: 95
} as const;

// キーの型を取得
type SubjectType = keyof typeof grades;
// 結果:'math' | 'english' | 'science'

// 使用例
let subject: SubjectType = 'math';     // OK
subject = 'english';                   // OK
subject = 'history';                   // エラー!

これは「成績をつけられる科目」を型として定義したことになります。

8. より実践的な例:商品カテゴリー

実際のウェブアプリでありそうな例を見ていきます

// 商品カテゴリーの定義
const PRODUCT_CATEGORIES = {
    books: {
        name: "本・コミック",
        icon: "📚"
    },
    electronics: {
        name: "家電・PC",
        icon: "💻"
    },
    food: {
        name: "食品",
        icon: "🍙"
    }
} as const;

// カテゴリーIDの型を作る('books' | 'electronics' | 'food')
type CategoryId = keyof typeof PRODUCT_CATEGORIES;

// 使用例
function showCategoryName(categoryId: CategoryId) {
    // カテゴリー名を表示
    const category = PRODUCT_CATEGORIES[categoryId];
    console.log(`${category.icon} ${category.name}`);
}

// 正しく動く
showCategoryName('books');        // 📚 本・コミック
showCategoryName('electronics');  // 💻 家電・PC

// エラーになる
showCategoryName('games');  // エラー!存在しないカテゴリー

9. 実際のWebアプリでの使用例

カテゴリー選択のプルダウンメニューを作る例です

// カテゴリー選択用のセレクトボックスを作る
function createCategorySelect() {
    // セレクトボックスのHTML
    const options = Object.entries(PRODUCT_CATEGORIES)
        .map(([id, category]) => (
            `<option value="${id}">
                ${category.icon} ${category.name}
            </option>`
        ))
        .join('');

    return `<select>${options}</select>`;
}

// 実際に使うとこんな感じのHTMLができる
/*
<select>
    <option value="books">📚 本・コミック</option>
    <option value="electronics">💻 家電・PC</option>
    <option value="food">🍙 食品</option>
</select>
*/

類似例

以下サンプルでここまでの理解を深めましょう

  1. 都道府県の情報を持つオブジェクトを作り、その型を生成します
// 1. まずオブジェクトを作る
const PREFECTURES = {
    tokyo: {
        name: "東京都",
        region: "関東"
    },
    osaka: {
        name: "大阪府",
        region: "関西"
    }
} as const;

// 2. 都道府県IDの型を作る
type PrefectureId = keyof typeof PREFECTURES;

// 3. 使ってみる
function showPrefectureName(prefId: PrefectureId) {
    console.log(PREFECTURES[prefId].name);
}

// 使用例
showPrefectureName('tokyo');  // "東京都"と表示される
showPrefectureName('osaka');  // "大阪府"と表示される
showPrefectureName('nagoya'); // エラー!

ここまでのまとめ

  • as const でオブジェクトを変更できないようにできる
  • keyof typeof でオブジェクトのキーから型を作れる
  • この機能は特にカテゴリーや選択肢のような決まった値を扱う時に便利
  • 実際のアプリでは、プルダウンメニューやバリデーションなどで活用できる

では、配列とオブジェクトを組み合わせた、より複雑な型の生成方法を見ていきます!

10. オブジェクトの配列を扱う

実際のアプリケーションでは、オブジェクトの配列を扱うことが多いです。
例えば、ユーザー一覧のようなデータです。

// ユーザー一覧のデータ
const USERS = [
    {
        id: 1,
        name: "田中太郎",
        role: "admin"
    },
    {
        id: 2,
        name: "山田花子",
        role: "user"
    },
    {
        id: 3,
        name: "佐藤次郎",
        role: "user"
    }
] as const;

// ユーザーのロール(役割)の型を作る
type UserRole = (typeof USERS)[number]["role"];
// 結果: "admin" | "user"

// IDの型を作る
type UserId = (typeof USERS)[number]["id"];
// 結果: 1 | 2 | 3

11. 実践的な例:商品一覧システム

ECサイトでよくある商品一覧を例に見てみましょう

// 商品データ
const PRODUCTS = [
    {
        id: "book1",
        name: "TypeScript入門",
        category: "books",
        price: 2800,
        status: "在庫あり"
    },
    {
        id: "food1",
        name: "お菓子セット",
        category: "food",
        price: 1200,
        status: "在庫なし"
    },
    {
        id: "game1",
        name: "ゲーム機本体",
        category: "electronics",
        price: 29800,
        status: "予約受付中"
    }
] as const;

// 商品ステータスの型を作る
type ProductStatus = (typeof PRODUCTS)[number]["status"];
// 結果: "在庫あり" | "在庫なし" | "予約受付中"

// 商品カテゴリーの型を作る
type ProductCategory = (typeof PRODUCTS)[number]["category"];
// 結果: "books" | "food" | "electronics"

// 実践的な使用例:商品の検索関数
function findProductsByStatus(status: ProductStatus) {
    return PRODUCTS.filter(product => product.status === status);
}

// 使用例
const inStockProducts = findProductsByStatus("在庫あり");
// エラーにならない
findProductsByStatus("在庫あり");     // OK
// エラーになる
findProductsByStatus("入荷待ち");     // エラー!

12. もっと実践的な例:APIレスポンスの型定義

Web APIからのレスポンスデータの型を定義する例もいってみます

// APIのレスポンスデータの例
const sampleApiResponse = {
    status: "success",
    data: {
        items: [
            {
                id: "item1",
                name: "商品A",
                variants: [
                    { color: "red", size: "S" },
                    { color: "red", size: "M" },
                    { color: "blue", size: "S" }
                ]
            }
        ],
        pagination: {
            current: 1,
            total: 5
        }
    }
} as const;

// 利用可能な色の型を取得
type AvailableColor = (typeof sampleApiResponse.data.items)[number]["variants"][number]["color"];
// 結果: "red" | "blue"

// 利用可能なサイズの型を取得
type AvailableSize = (typeof sampleApiResponse.data.items)[number]["variants"][number]["size"];
// 結果: "S" | "M"

// この型を使った関数の例
function isValidVariant(color: AvailableColor, size: AvailableSize): boolean {
    return sampleApiResponse.data.items[0].variants.some(
        v => v.color === color && v.size === size
    );
}

// 使用例
console.log(isValidVariant("red", "S"));    // true
console.log(isValidVariant("blue", "M"));   // false
console.log(isValidVariant("green", "L"));  // エラー!

13. よくある使用パターン:フォーム入力の検証

ユーザーフォームでの入力値の検証によく使われる例

// フォームの選択肢定義
const FORM_CONFIG = {
    gender: ["男性", "女性", "その他", "回答しない"] as const,
    prefecture: ["東京都", "大阪府", "愛知県", /* ... */] as const,
    contactTime: ["午前", "午後", "夜間"] as const
} as const;

// 各フィールドの型を作成
type GenderType = (typeof FORM_CONFIG.gender)[number];
type PrefectureType = (typeof FORM_CONFIG.prefecture)[number];
type ContactTimeType = (typeof FORM_CONFIG.contactTime)[number];

// フォームデータの型
interface FormData {
    name: string;
    gender: GenderType;
    prefecture: PrefectureType;
    contactTime: ContactTimeType;
}

// フォームの検証関数
function validateForm(data: FormData) {
    // 性別の検証
    if (!FORM_CONFIG.gender.includes(data.gender)) {
        throw new Error("無効な性別が選択されています");
    }
    // 都道府県の検証
    if (!FORM_CONFIG.prefecture.includes(data.prefecture)) {
        throw new Error("無効な都道府県が選択されています");
    }
    // 希望連絡時間の検証
    if (!FORM_CONFIG.contactTime.includes(data.contactTime)) {
        throw new Error("無効な希望連絡時間が選択されています");
    }
}

// 使用例
const formData = {
    name: "田中太郎",
    gender: "男性",
    prefecture: "東京都",
    contactTime: "午前"
};

validateForm(formData);  // OK

// エラーになる例
validateForm({
    name: "田中太郎",
    gender: "男性",
    prefecture: "架空県",     // エラー!
    contactTime: "深夜"      // エラー!
});

類似例

次のタスク管理システムのサンプルで理解を整理します

  1. まずデータを定義
const TASKS = [
    {
        id: 1,
        title: "報告書作成",
        priority: "高",
        status: "進行中",
        category: "仕事"
    },
    {
        id: 2,
        title: "買い物",
        priority: "中",
        status: "未着手",
        category: "個人"
    }
] as const;
  1. 必要な型を作る
// 優先度の型
type TaskPriority = (typeof TASKS)[number]["priority"];
// "高" | "中"

// ステータスの型
type TaskStatus = (typeof TASKS)[number]["status"];
// "進行中" | "未着手"

// カテゴリーの型
type TaskCategory = (typeof TASKS)[number]["category"];
// "仕事" | "個人"
  1. 型を使った関数を作る
// タスクをフィルタリングする関数
function filterTasks(category: TaskCategory, status: TaskStatus) {
    return TASKS.filter(task => 
        task.category === category && 
        task.status === status
    );
}

// 使用例
const workInProgress = filterTasks("仕事", "進行中");

まとめ

  • 配列とオブジェクトを組み合わせて、複雑なデータの型を作れる
  • これらの型は特にフォームの入力値検証や、APIのレスポンス処理で役立つ
  • データと型を一箇所で管理することで、保守性が向上する

型を活用することで、開発時のミスを防ぎ、より安全なコードを書くことができます!

さいごに:あなたのコードが型安全に進化する

基本の型定義 から始まり、
🎯 配列やオブジェクトの型 を使いこなし、
🚀 実践的なWebアプリケーション での活用方法まで。

お疲れ様でした!
特に重要なのは、

  • 型定義は「面倒な制約」ではなく、「頼れる味方」だということ
  • 自分で型を作れるようになると、コードがより安全で明確になること
  • エラーメッセージが、バグを早期に見つける助けになること

です。

きっと数週間後には、「型があると安心!」と感じる瞬間が増えているはずです。
そして気づいたら、TypeScriptでコードを書くのが自然に楽しくなっているかもしれません。

Discussion