まずこれだけ覚えれば実務の大半は足ります。
| 型 | 意味 | 例 |
|---|---|---|
Partial<T> | すべてのプロパティを省略可能にする | 更新用の型 |
Required<T> | すべてを必須にする | 既定値で埋めた後の型 |
Readonly<T> | すべてを読み取り専用にする(1段階だけ) | 設定オブジェクト |
Pick<T, K> | 指定したキーだけ取り出す | Pick<User, "id" | "name"> |
Omit<T, K> | 指定したキーを除く | Omit<User, "password"> |
Record<K, V> | キーと値の型を指定した辞書 | Record<string, number> |
ReturnType<F> | 関数の戻り値の型 | ReturnType<typeof f> |
Parameters<F> | 関数の引数の型(タプル) | Parameters<typeof f>[0] |
Awaited<T> | Promise を剥がした型 | Awaited<ReturnType<typeof load>> |
NonNullable<T> | null・undefined を除く | NonNullable<string \| null> |
Exclude<T, U> | union から取り除く | Exclude<Status, "closed"> |
Extract<T, U> | union から取り出す | Extract<Shape, { kind: "circle" }> |
type User = { id: string; name: string; password: string; age?: number };
// 外部に返す形(password を除く)
type PublicUser = Omit<User, "password">;
// 更新用(id 以外を省略可能に)
type UserPatch = Partial<Omit<User, "id">>;
// 関数の戻り値から型を取る
async function loadUser(): Promise<PublicUser> { /* ... */ }
type Loaded = Awaited<ReturnType<typeof loadUser>>; // PublicUser
Readonly<T> は1段階しか効きません。
type Config = Readonly<{ db: { host: string } }>;
const c: Config = { db: { host: "localhost" } };
c.db = { host: "x" }; // エラー(1段階目は守られる)
c.db.host = "x"; // 通ってしまう(2段階目は守られない)
入れ子まで守るには DeepReadonly を自分で書きます(A2.5)。
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
[K in keyof T] は「T のキーをすべて回す」という意味です。for...of の型版だと考えてください。
| 記法 | 意味 |
|---|---|
[K in keyof T] | T のキーを回す。K が各キーの名前 |
T[K] | キー K の値の型 |
readonly / ? | 付けると全キーに適用される |
-readonly / -? | 外す(Required<T> の実装がこれ) |
as | キー名を変換する |
type Partial<T> = { [K in keyof T]?: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] }; // ? を外す
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Record<K extends keyof any, V> = { [P in K]: V };
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type User = { name: string; age: number };
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }
// 関数のプロパティだけを抜き出す
type FunctionKeys<T> = {
[K in keyof T]: T[K] extends (...args: never[]) => unknown ? K : never;
}[keyof T];
type Api = { load(): void; name: string; save(): void };
type Methods = FunctionKeys<Api>; // "load" | "save"
{ ... }[keyof T] という形を覚えてください。
「各キーに対して型を作り、最後に全部を union にする」という意味です。never は union の中で消えるため、条件に合わないキーが除かれます。この形はライブラリの型定義で頻出します。
type IsString<T> = T extends string ? "はい" : "いいえ";
type A = IsString<"abc">; // "はい"
type B = IsString<123>; // "いいえ"
T extends U ? X : Y は「T が U に代入できるなら X、できなければ Y」です。
type ToArray<T> = T extends unknown ? T[] : never;
type R = ToArray<string | number>;
// string[] | number[] ((string | number)[] ではない)
型引数が裸の union だと、1つずつに分配されます。
これが Exclude や NonNullable の仕組みです。
type Exclude<T, U> = T extends U ? never : T;
type S = Exclude<"a" | "b" | "c", "b">;
// "a" | never | "c" → never は消えるので "a" | "c"
分配してほしくないときは [T] extends [U] と角括弧で囲みます。
type ElementOf<T> = T extends (infer U)[] ? U : never;
type E = ElementOf<string[]>; // string
infer U は「ここに当てはまる型を U という名前で捕まえる」という意味です。正規表現のキャプチャに似ています。
type ReturnType<T> = T extends (...args: never[]) => infer R ? R : never;
type Parameters<T> = T extends (...args: infer P) => unknown ? P : never;
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T; // 再帰する
// 判別可能ユニオンから特定の1つを取り出す
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number };
type ShapeOf<K extends Shape["kind"]> = Extract<Shape, { kind: K }>;
type Circle = ShapeOf<"circle">; // { kind: "circle"; radius: number }
function area(s: ShapeOf<"circle">): number {
return Math.PI * s.radius ** 2;
}
ここまでが実用の範囲です。
これ以上(型レベルでの再帰・文字列の分解・算術)は、ライブラリを書く人の領域です。アプリケーションのコードで必要になることはほとんどありません。読めれば十分です。
type Prefixed = `user-${string}`;
const a: Prefixed = "user-001"; // OK
const b: Prefixed = "order-001"; // エラー TS2322
type Lang = "ja" | "en";
type Page = "home" | "about";
type Path = `/${Lang}/${Page}`;
// "/ja/home" | "/ja/about" | "/en/home" | "/en/about"
| 型 | 働き | 例 |
|---|---|---|
Uppercase<S> | 大文字に | "abc" → "ABC" |
Lowercase<S> | 小文字に | "ABC" → "abc" |
Capitalize<S> | 先頭だけ大文字に | "abc" → "Abc" |
Uncapitalize<S> | 先頭だけ小文字に | "ABC" → "aBC" |
// イベント名の規約を型で表す
type EventName = `on${Capitalize<"click" | "focus">}`;
// "onClick" | "onFocus"
// 環境変数の接頭辞
type PublicEnvKey = `PUBLIC_${Uppercase<string>}`;
// CSS のプロパティ
type Margin = `margin${"" | "Top" | "Right" | "Bottom" | "Left"}`;
// "margin" | "marginTop" | "marginRight" | "marginBottom" | "marginLeft"
組み合わせの数に注意してください。
union どうしを掛け合わせると組み合わせの数だけ型が生成されます。10 × 10 × 10 なら 1000 通りです。数が多すぎると TypeScript が「複雑すぎる」というエラーを出し、エディタも遅くなります。
type Split<S extends string, D extends string> =
S extends `${infer Head}${D}${infer Tail}`
? [Head, ...Split<Tail, D>]
: [S];
type Parts = Split<"a/b/c", "/">; // ["a", "b", "c"]
これは「読めればよい」の代表例です。
ルーティングライブラリの型定義("/users/:id" からパラメータ名を取り出す等)で使われています。自分で書く機会はまずありません。「文字列を型のレベルで分解している」と分かれば十分です。
| 水準 | 内容 | 目標 |
|---|---|---|
| 1 | 組み込みユーティリティ型(A4.1) | 書ける 実務で日常的に使う |
| 2 | 簡単な Mapped Types(A4.2 前半) | 書ける 必要になれば書く |
| 3 | Conditional Types と infer(A4.3) | 読める 型定義を読むときに要る |
| 4 | テンプレートリテラル型の分解(A4.4 後半) | 読めなくてよい ライブラリ作者の領域 |