型の絞り込み
TypeScript の型ガードや制御フロー分析で、ユニオン型をより具体的な型に絞り込む手法
TypeScript型システム
型の絞り込みとは
型の絞り込み (Type Narrowing) は、TypeScript の制御フロー分析により、ユニオン型 (string | number | null) をより具体的な型 (string) に絞り込む仕組みである。if 文や typeof チェックの後、TypeScript コンパイラが自動的に型を推論する。
function process(value: string | number | null) {
// ここでは value は string | number | null
if (value === null) return;
// ここでは value は string | number (null が除外された)
if (typeof value === 'string') {
// ここでは value は string
console.log(value.toUpperCase());
} else {
// ここでは value は number
console.log(value.toFixed(2));
}
}
絞り込みの方法
typeof ガード
function format(value: string | number): string {
if (typeof value === 'string') return value.trim();
return value.toFixed(2); // number に絞り込まれる
}
instanceof ガード
function handleError(err: Error | string) {
if (err instanceof Error) {
console.error(err.message, err.stack);
} else {
console.error(err);
}
}
in 演算子
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ('swim' in animal) animal.swim(); // Fish に絞り込み
else animal.fly(); // Bird に絞り込み
}
Discriminated Union (判別共用体)
最も強力な絞り込みパターン。共通の type フィールドで型を判別する。
type ApiResponse =
| { type: 'success'; data: User }
| { type: 'error'; message: string }
| { type: 'loading' };
function render(response: ApiResponse) {
switch (response.type) {
case 'success': return <UserProfile user={response.data} />;
case 'error': return <ErrorMessage message={response.message} />;
case 'loading': return <Spinner />;
}
}
カスタム型ガード
function isUser(value: unknown): value is User {
return typeof value === 'object' && value !== null && 'id' in value && 'name' in value;
}
const data: unknown = JSON.parse(body);
if (isUser(data)) {
console.log(data.name); // User 型に絞り込まれる
}
網羅性チェック (Exhaustive Check)
never 型を使って、switch 文で全ケースを処理していることをコンパイル時に保証する。
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
function handleStatus(status: 'active' | 'inactive' | 'suspended') {
switch (status) {
case 'active': return 'アクティブ';
case 'inactive': return '非アクティブ';
case 'suspended': return '停止中';
default: return assertNever(status); // 新しいステータスを追加し忘れるとコンパイルエラー
}
}
絞り込みが効かないケース
// ❌ コールバック内では絞り込みが効かない
function process(value: string | null) {
if (value !== null) {
setTimeout(() => {
value.toUpperCase(); // エラー: value は string | null のまま
}, 100);
}
}
// ✅ ローカル変数に代入して解決
function process(value: string | null) {
if (value !== null) {
const v = value; // v は string に絞り込まれる
setTimeout(() => { v.toUpperCase(); }, 100);
}
}
型ガードの種類
| 型ガード | 構文 | 用途 |
|---|---|---|
| typeof | typeof x === 'string' |
プリミティブ型 |
| instanceof | x instanceof Error |
クラス |
| in | 'name' in x |
プロパティの存在 |
| Discriminated Union | x.kind === 'circle' |
ユニオン型 |
| カスタム型ガード | function isUser(x): x is User |
複雑な判定 |
型の絞り込みについては関連書籍でも詳しく扱われている。