「型を書く」から「型で設計する」へ。初心者が本物のTSエンジニアになるための地図。
はじめに
TypeScriptを「JavaScriptに型をつけたもの」だと思っていませんか?
それは半分正解で、半分大きな誤解です。
TypeScriptの真の力は 「コンパイル時に間違いを発見する」 ことではなく、「型を使ってシステム全体の設計を表現する」 ことにあります。
このロードマップでは、初心者が現場で通用する最強のTSエンジニアになるための道筋を、4つのステージに分けて解説します。
Stage 1 ― 型の基礎を「本当に」理解する
1-1. プリミティブ型と型推論
多くの初心者がここで躓く「型を書きすぎる」問題から始めましょう。
// ❌ 悪い例:型を全部書こうとする
const name: string = "Masaki";
const age: number = 30;
const isActive: boolean = true;
// ✅ 良い例:TypeScriptに推論させる
const name = "Masaki"; // string と推論される
const age = 30; // number と推論される
const isActive = true; // boolean と推論される
型推論を信頼するのが最強TSエンジニアへの第一歩です。型アノテーションは「推論できない場所」にだけ書きます。
1-2. any は麻薬である
// ❌ any を使うとTSの恩恵がゼロになる
function processData(data: any) {
return data.someMethod(); // 実行時エラーが起きても気づけない
}
// ✅ unknown を使う(型チェックを強制できる)
function processData(data: unknown) {
if (typeof data === "string") {
return data.toUpperCase(); // ここでは string と確定している
}
throw new Error("Unexpected data type");
}
any を使ってよいのは「型定義のない古いライブラリを暫定的につなぐ時」だけです。それ以外は unknown か、適切な型を定義してください。
1-3. Union型とNarrowingをマスターする
type Status = "loading" | "success" | "error";
function renderUI(status: Status) {
// TypeScriptが網羅チェックをしてくれる
switch (status) {
case "loading":
return <Spinner />;
case "success":
return <Content />;
case "error":
return <ErrorMessage />;
// default不要:全ケース網羅でコンパイルエラーにならない
}
}
Stage 1 のゴール:型推論・Union型・Narrowingを使い、any なしでコードが書ける。
Stage 2 ― 型システムを「設計ツール」として使う
2-1. interfaceとtypeの使い分け
// interface:オブジェクトの形を定義する(拡張前提)
interface User {
id: string;
name: string;
}
interface AdminUser extends User {
permissions: string[];
}
// type:Union・交差型など複雑な型を定義する
type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
// 使い方
function fetchUser(id: string): Result<User> {
// ...
}
迷ったら:オブジェクト構造には interface、それ以外の複合型には type を使うのが現場のコンセンサスです。
2-2. Generics(ジェネリクス)で再利用可能な型を作る
ジェネリクスはTypeScriptで「型レベルの関数」です。
// ❌ 型ごとに同じ関数を書く
function getFirstString(arr: string[]): string | undefined {
return arr[0];
}
function getFirstNumber(arr: number[]): number | undefined {
return arr[0];
}
// ✅ ジェネリクスで1つにまとめる
function getFirst<T>(arr: T[]): T | undefined {
return arr[0];
}
const first = getFirst([1, 2, 3]); // T = number と推論
const name = getFirst(["a", "b"]); // T = string と推論
2-3. Utility Typesを使いこなす
TypeScriptには便利な組み込み型が多数あります。これを知らずに自分で実装するのは時間の無駄です。
interface User {
id: string;
name: string;
email: string;
password: string;
}
// Partial:全プロパティをオプショナルに
type UpdateUserInput = Partial<User>;
// Pick:必要なプロパティだけ取り出す
type UserPreview = Pick<User, "id" | "name">;
// Omit:特定のプロパティを除外する
type PublicUser = Omit<User, "password">;
// Required:全プロパティを必須に
type StrictUser = Required<User>;
// Readonly:変更不可にする
type ImmutableUser = Readonly<User>;
// Record:keyとvalueの型を指定したオブジェクト
type UserMap = Record<string, User>;
Stage 2 のゴール:Generics・Utility Typesを使い、DRYな型定義が書ける。
Stage 3 ― 型で「バグを不可能にする」設計をする
ここからが本物のTSエンジニアの領域です。
3-1. Discriminated Union で状態管理を安全にする
// ❌ 悪い例:フラグが増えると破綻する
type FetchState = {
isLoading: boolean;
data: User | null;
error: string | null;
};
// isLoading: true かつ data: User という矛盾した状態が作れてしまう
// ✅ 良い例:Discriminated Union で状態を排他的にする
type FetchState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: User }
| { status: "error"; error: string };
function render(state: FetchState) {
switch (state.status) {
case "idle":
return null;
case "loading":
return <Spinner />;
case "success":
return <UserCard user={state.data} />; // data が必ず存在する
case "error":
return <ErrorMessage message={state.error} />; // error が必ず存在する
}
}
これで「ローディング中にデータが存在する」という不正な状態を型レベルで表現不可能にできます。
3-2. Template Literal Types でAPIルートを型安全にする
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiVersion = "v1" | "v2";
type Resource = "users" | "posts" | "comments";
type ApiRoute = `/${ApiVersion}/${Resource}`;
// => "/v1/users" | "/v1/posts" | "/v1/comments" | "/v2/users" | ...
function apiCall(method: HttpMethod, route: ApiRoute) {
// route は有効なAPIパスのみ受け付ける
}
apiCall("GET", "/v1/users"); // ✅ OK
apiCall("GET", "/v3/users"); // ❌ コンパイルエラー! /v3 は存在しない
3-3. satisfies 演算子で型チェックと推論を両立する(TS 4.9+)
const config = {
theme: "dark",
language: "ja",
maxRetries: 3,
} satisfies Record<string, string | number>;
// satisfies を使うと:
// 1. Record<string, string | number> の制約でチェックされる
// 2. 推論は元の型を保持(config.theme は string ではなく "dark" のまま)
config.theme; // 型は "dark"(satisfies なしなら string になってしまう)
Stage 3 のゴール:型で不正な状態を表現不可能にする設計ができる。
Stage 4 ― 型レベルプログラミングで「型を計算する」
最強の領域です。ここまで来ると型パズルが楽しくなります。
4-1. Conditional Types
// T が配列なら要素の型を返す、そうでなければ T をそのまま返す
type Unwrap<T> = T extends Array<infer Item> ? Item : T;
type A = Unwrap<string[]>; // string
type B = Unwrap<number>; // number
type C = Unwrap<User[]>; // User
4-2. Mapped Types でオブジェクト型を変換する
// 全プロパティを関数に変換する
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface User {
name: string;
age: number;
}
type UserGetters = Getters<User>;
// => { getName: () => string; getAge: () => number }
4-3. infer で型を「分解」する
// 関数の戻り値の型を取り出す(組み込みReturnTypeの自作)
type MyReturnType<T extends (...args: any[]) => any> =
T extends (...args: any[]) => infer R ? R : never;
async function fetchUser(): Promise<User> {
// ...
}
type FetchResult = MyReturnType<typeof fetchUser>; // Promise<User>
type Awaited = Awaited<FetchResult>; // User
4-4. 実践:zodで実行時型検証を型と連携させる
import { z } from "zod";
// zodスキーマが型定義を兼ねる
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(50),
email: z.string().email(),
age: z.number().int().min(0).max(150),
});
// スキーマから型を自動生成
type User = z.infer<typeof UserSchema>;
// APIレスポンスの検証
async function getUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return UserSchema.parse(data); // 失敗すれば例外、成功すれば型安全
}
Stage 4 のゴール:型を計算・変換し、zodなど外部ライブラリと連携した堅牢な型設計ができる。
まとめ:最強TSエンジニアの思考フレームワーク
| ステージ | 指標 | キーワード |
|---|---|---|
| Stage 1 | any なしで書ける | 型推論・Union・Narrowing |
| Stage 2 | DRYな型が書ける | Generics・Utility Types |
| Stage 3 | 不正状態を型で封じる | Discriminated Union・satisfies |
| Stage 4 | 型を計算・変換できる | Conditional Types・infer・zod |
最強のTSエンジニアとは**「型によってバグを発生不可能にする設計ができる人」**です。
ぜひこのロードマップを参考に、一歩ずつ型の力を身につけてください。
参考リソース
この記事が参考になったら、いいね・ストック・フォローをお願いします!