TypeScript 型縮小(narrowing)パターン早見表:`in`/`instanceof`/`is`/`asserts`完全対応

TypeScript で開発していると、「この変数の型がもう少し具体的にわかれば...」と感じる場面がよくありますね。そんな時に威力を発揮するのが型縮小(Type Narrowing)です。今回は実践的に使える 4 つの主要パターンを、具体的なコード例とともに詳しく解説いたします。
型縮小パターン早見表
まずは 4 つの主要パターンを一覧で確認しましょう。
パターン | 構文例 | 主な用途 | 特徴 |
---|---|---|---|
in オペレータ | 'property' in obj | オブジェクトのプロパティ存在チェック | シンプルで直感的。Union 型の判定に最適 |
instanceof オペレータ | obj instanceof Class | クラスインスタンスの判定 | 継承関係に対応。オブジェクト指向設計で有効 |
is 型ガード | (x): x is Type | カスタム条件による判定 | 柔軟性が高い。複雑なロジックを再利用可能 |
asserts アサーション | asserts x is Type | バリデーション+型保証 | 例外処理と型安全性を統合 |
使い分けの基本指針
mermaidflowchart LR
start["型縮小が必要"] --> simple{"シンプルな<br/>プロパティ判定?"};
simple --|Yes|--> in_choice["in オペレータ"];
simple --|No|--> class_check{"クラスの<br/>インスタンス?"};
class_check --|Yes|--> instanceof_choice["instanceof"];
class_check --|No|--> complex{"複雑な条件や<br/>再利用性重視?"};
complex --|Yes|--> is_choice["is 型ガード"];
complex --|No|--> validation{"バリデーション<br/>必須?"};
validation --|Yes|--> asserts_choice["asserts"];
validation --|No|--> other["typeof など<br/>その他手法"];
各パターンのメリット・デメリット
パターン | ✅ メリット | ⚠️ 注意点 |
---|---|---|
in | ・コードが簡潔・TypeScript が自動推論・実行コストが低い | ・optional プロパティでは効果薄・文字列リテラル必須 |
instanceof | ・継承チェーンに対応・オブジェクトの出自が明確・実行コストが低い | ・プリミティブ型では使用不可・クラス設計への依存 |
is | ・柔軟性が非常に高い・ロジックの再利用可能・複雑な条件に対応 | ・実装コストが高め・条件の妥当性は自己責任 |
asserts | ・型安全性とランタイムチェックの統合・バリデーション処理が明確 | ・例外処理が必要・パフォーマンス考慮要 |
型縮小とは何か
型縮小とは、Union 型や any 型など広い型から、より具体的で限定された型へと絞り込む仕組みのことです。TypeScript が実行時の条件分岐を解析して、その分岐内では特定の型であることを保証してくれる機能ですね。
以下の図で、型縮小の基本的な概念を確認してみましょう。
mermaidflowchart TD
union["Union型<br/>string | number | null"] --> check{"条件チェック"}
check -->|"typeof value === 'string'"| str["string型に縮小"]
check -->|"typeof value === 'number'"| num["number型に縮小"]
check -->|"value === null"| nil["null型に縮小"]
この図が示すように、条件チェックによって広い型から具体的な型へと段階的に絞り込まれていきます。
型縮小を活用することで、以下のようなメリットが得られます:
- 型安全性の向上:実行時エラーの予防
- 開発体験の改善:適切な補完機能の提供
- コードの可読性向上:意図が明確になる
各パターンの詳細解説
TypeScript で使える型縮小パターンは数多くありますが、実務で頻繁に活用される 4 つの手法を体系的に見ていきましょう。
プロパティ存在チェック:in
オペレータ
in
オペレータは、オブジェクトに特定のプロパティが存在するかどうかをチェックして型を縮小する手法です。Union 型のオブジェクトを扱う際に特に有効ですね。
基本的な使い方
in
オペレータの基本的な構文を確認してみましょう。
typescript// 基本的な型定義
type Cat = {
name: string;
meow: () => void;
};
type Dog = {
name: string;
bark: () => void;
};
type Animal = Cat | Dog;
プロパティの存在チェックによる型縮小の実装は以下のようになります:
typescriptfunction makeSound(animal: Animal) {
if ('meow' in animal) {
// この分岐内では animal は Cat型として扱われる
animal.meow(); // TypeScriptが meow メソッドの存在を保証
console.log(`${animal.name} が鳴きました`);
} else {
// else分岐では自動的に Dog型として推論される
animal.bark(); // bark メソッドが安全に呼び出せる
console.log(`${animal.name} が吠えました`);
}
}
オブジェクト型での実装例
より実践的な例として、API レスポンスの処理を見てみましょう。
typescript// APIレスポンスの型定義
type SuccessResponse = {
status: 'success';
data: {
id: number;
name: string;
};
};
type ErrorResponse = {
status: 'error';
message: string;
code: number;
};
type ApiResponse = SuccessResponse | ErrorResponse;
レスポンス処理の実装では、プロパティの存在で型を判定できます:
typescriptfunction handleApiResponse(response: ApiResponse) {
if ('data' in response) {
// SuccessResponse型として処理
console.log(
`成功: ユーザー${response.data.name}を取得`
);
return response.data;
} else {
// ErrorResponse型として処理
console.error(
`エラー ${response.code}: ${response.message}`
);
throw new Error(response.message);
}
}
ネストしたオブジェクトでも同様に活用できます:
typescripttype UserProfile = {
basic: {
name: string;
email: string;
};
premium?: {
subscriptionId: string;
features: string[];
};
};
function displayUserInfo(profile: UserProfile) {
console.log(`ユーザー: ${profile.basic.name}`);
if ('premium' in profile && profile.premium) {
// プレミアム機能の表示
console.log(
`プレミアム機能: ${profile.premium.features.join(
', '
)}`
);
}
}
注意点と制限事項
in
オペレータを使用する際の重要な注意点をご紹介します。
プロパティ名の文字列リテラル プロパティ名は文字列リテラルで指定する必要があります:
typescript// ❌ 変数を使用した場合は型縮小されない
const property = 'meow';
if (property in animal) {
// animal の型は Animal のまま(縮小されない)
}
// ✅ 文字列リテラルを直接使用
if ('meow' in animal) {
// 正常に Cat型に縮小される
}
optional プロパティでの挙動
optional プロパティ(?
付き)では期待通りに動作しない場合があります:
typescripttype OptionalProp = {
name: string;
age?: number; // optional プロパティ
};
function checkOptional(obj: OptionalProp) {
if ('age' in obj) {
// obj.age は number | undefined のまま
// undefined の可能性が残る
console.log(obj.age.toString()); // ❌ エラーの可能性
}
}
インスタンス判定:instanceof
オペレータ
instanceof
オペレータは、オブジェクトが特定のクラスのインスタンスかどうかを判定して型縮小を行う手法です。クラスベースの設計で威力を発揮しますね。
クラスインスタンスでの型縮小
基本的なクラス定義から見てみましょう:
typescriptclass User {
constructor(public name: string) {}
greet() {
return `こんにちは、${this.name}です`;
}
}
class Admin {
constructor(
public name: string,
public permissions: string[]
) {}
manageUsers() {
return `${this.name} がユーザーを管理中`;
}
}
instanceof
を使った型縮小の実装:
typescripttype UserType = User | Admin;
function handleUser(user: UserType) {
if (user instanceof Admin) {
// この分岐内では user は Admin型
console.log(user.manageUsers());
console.log(`権限: ${user.permissions.join(', ')}`);
} else {
// else分岐では User型として推論
console.log(user.greet());
}
}
// 使用例
const regularUser = new User('田中');
const adminUser = new Admin('佐藤', [
'read',
'write',
'delete',
]);
handleUser(regularUser); // "こんにちは、田中です"
handleUser(adminUser); // "佐藤 がユーザーを管理中"
継承関係での活用
継承関係がある場合の型縮小パターンを確認しましょう:
typescriptclass Animal {
constructor(public name: string) {}
makeSound() {
return `${this.name} が音を出している`;
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
bark() {
return `${this.name}(${this.breed})がワンワン`;
}
}
class Cat extends Animal {
constructor(name: string, public isIndoor: boolean) {
super(name);
}
meow() {
const location = this.isIndoor ? '室内' : '外';
return `${this.name} が${location}でニャーニャー`;
}
}
継承チェーンでの型縮小処理:
typescriptfunction animalAction(animal: Animal) {
if (animal instanceof Dog) {
// Dog型に縮小(Animalのメソッドも使用可能)
console.log(animal.bark());
console.log(`犬種: ${animal.breed}`);
} else if (animal instanceof Cat) {
// Cat型に縮小
console.log(animal.meow());
console.log(
`飼育環境: ${animal.isIndoor ? '室内' : '屋外'}`
);
} else {
// 基底クラス Animal として処理
console.log(animal.makeSound());
}
}
プリミティブ型での制限
instanceof
オペレータはオブジェクト型でのみ有効で、プリミティブ型では期待通りに動作しません:
typescript// ❌ プリミティブ型では正常に動作しない
function checkPrimitive(value: string | number) {
if (value instanceof String) {
// これは期待通りに動作しない
// 通常の文字列リテラルは String クラスのインスタンスではない
}
}
// ✅ プリミティブ型には typeof を使用
function checkPrimitiveCorrect(value: string | number) {
if (typeof value === 'string') {
// 正常に string型に縮小される
console.log(value.toUpperCase());
} else {
// number型として処理
console.log(value.toFixed(2));
}
}
組み込みオブジェクトとの使い分けも重要です:
typescript// Date オブジェクトの判定(✅ 正常動作)
function processValue(value: string | Date) {
if (value instanceof Date) {
// Date型に縮小
console.log(value.toISOString());
} else {
// string型として処理
console.log(value.length);
}
}
// Array の判定も可能(✅ 正常動作)
function handleData(data: string | string[]) {
if (data instanceof Array) {
// string[]型に縮小
console.log(`配列の長さ: ${data.length}`);
data.forEach((item) => console.log(item));
} else {
// string型として処理
console.log(`文字列: ${data}`);
}
}
カスタム型ガード:is
キーワード
is
キーワードを使用したカスタム型ガードは、独自の条件ロジックで型縮小を実現する強力な手法です。複雑な判定条件や再利用可能な型チェック関数を作成できます。
ユーザー定義型ガードの作成
まず、基本的な型ガード関数の構文を確認しましょう:
typescript// 基本的な型定義
interface User {
id: number;
name: string;
email: string;
}
interface Admin {
id: number;
name: string;
permissions: string[];
}
type UserOrAdmin = User | Admin;
is
キーワードを使った型ガード関数の実装:
typescript// Admin型かどうかを判定する型ガード関数
function isAdmin(user: UserOrAdmin): user is Admin {
// permissions プロパティの存在で判定
return 'permissions' in user;
}
// User型かどうかを判定する型ガード関数
function isUser(user: UserOrAdmin): user is User {
// email プロパティの存在で判定
return 'email' in user;
}
型ガード関数を使用した処理の実装:
typescriptfunction processUserData(user: UserOrAdmin) {
if (isAdmin(user)) {
// この分岐内では user は Admin型として扱われる
console.log(`管理者: ${user.name}`);
console.log(`権限: ${user.permissions.join(', ')}`);
user.permissions.forEach((permission) => {
console.log(`- ${permission}`);
});
} else if (isUser(user)) {
// この分岐内では user は User型として扱われる
console.log(`ユーザー: ${user.name}`);
console.log(`メール: ${user.email}`);
}
}
複雑な条件での型縮小
より複雑な判定ロジックを含む型ガードの例を見てみましょう:
typescript// APIレスポンス型の定義
interface ApiSuccessResponse {
success: true;
data: {
id: number;
timestamp: string;
};
metadata?: {
version: string;
cached: boolean;
};
}
interface ApiErrorResponse {
success: false;
error: {
code: string;
message: string;
};
retryAfter?: number;
}
type ApiResponse = ApiSuccessResponse | ApiErrorResponse;
複雑な条件を含む型ガード関数の実装:
typescript// 成功レスポンスかどうかを詳細に判定
function isSuccessResponse(
response: ApiResponse
): response is ApiSuccessResponse {
return (
response.success === true &&
'data' in response &&
typeof response.data === 'object' &&
response.data !== null &&
'id' in response.data &&
'timestamp' in response.data
);
}
// エラーレスポンスかどうかを判定
function isErrorResponse(
response: ApiResponse
): response is ApiErrorResponse {
return (
response.success === false &&
'error' in response &&
typeof response.error === 'object' &&
'code' in response.error &&
'message' in response.error
);
}
型ガードを活用したエラーハンドリング:
typescriptasync function handleApiResponse(response: ApiResponse) {
if (isSuccessResponse(response)) {
// ApiSuccessResponse型として安全に処理
console.log(`データID: ${response.data.id}`);
console.log(`取得時刻: ${response.data.timestamp}`);
if (response.metadata) {
console.log(
`バージョン: ${response.metadata.version}`
);
console.log(
`キャッシュ: ${
response.metadata.cached ? 'あり' : 'なし'
}`
);
}
return response.data;
} else if (isErrorResponse(response)) {
// ApiErrorResponse型として処理
console.error(
`エラー ${response.error.code}: ${response.error.message}`
);
if (response.retryAfter) {
console.log(
`${response.retryAfter}秒後にリトライ可能`
);
}
throw new Error(`API Error: ${response.error.message}`);
}
}
再利用可能な型ガード設計
汎用的で再利用しやすい型ガード関数の設計パターンをご紹介します:
typescript// 汎用的なプロパティ存在チェック型ガード
function hasProperty<T, K extends string>(
obj: T,
prop: K
): obj is T & Record<K, unknown> {
return (
typeof obj === 'object' && obj !== null && prop in obj
);
}
// 型付きプロパティチェック
function hasPropertyOfType<T, K extends string, V>(
obj: T,
prop: K,
type: string
): obj is T & Record<K, V> {
return (
hasProperty(obj, prop) &&
typeof (obj as any)[prop] === type
);
}
汎用型ガードの活用例:
typescript// 様々なオブジェクト型で再利用可能
function processUnknownObject(obj: unknown) {
if (
hasPropertyOfType<unknown, 'name', string>(
obj,
'name',
'string'
)
) {
// obj は { name: string } を含む型として扱われる
console.log(`名前: ${obj.name}`);
}
if (
hasPropertyOfType<unknown, 'age', number>(
obj,
'age',
'number'
)
) {
// obj は { age: number } を含む型として扱われる
console.log(`年齢: ${obj.age}歳`);
}
if (
hasProperty(obj, 'skills') &&
Array.isArray(obj.skills)
) {
// 配列かどうかのチェックも組み合わせ可能
console.log(`スキル数: ${obj.skills.length}`);
}
}
配列要素の型ガードパターン:
typescript// 配列の全要素が特定の型かチェック
function isArrayOf<T>(
arr: unknown[],
typeGuard: (item: unknown) => item is T
): arr is T[] {
return arr.every(typeGuard);
}
// 文字列配列かどうかのチェック
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function processStringArray(data: unknown) {
if (Array.isArray(data) && isArrayOf(data, isString)) {
// data は string[] として安全に処理可能
data.forEach((str) => console.log(str.toUpperCase()));
}
}
アサーション関数:asserts
キーワード
asserts
キーワードを使用したアサーション関数は、条件が満たされない場合に例外を投げることで型縮小を実現する手法です。バリデーション処理と型安全性を同時に確保できる強力な機能ですね。
void 型を返す型縮小
基本的なアサーション関数の構文を確認しましょう:
typescript// 基本的なアサーション関数
function assertIsString(
value: unknown
): asserts value is string {
if (typeof value !== 'string') {
throw new Error(`Expected string, got ${typeof value}`);
}
}
// 使用例
function processStringValue(input: unknown) {
assertIsString(input);
// この行以降、input は string型として扱われる
console.log(input.toUpperCase());
console.log(`文字数: ${input.length}`);
}
null・undefined チェックのアサーション関数:
typescript// null・undefined チェック用アサーション
function assertNotNull<T>(
value: T | null | undefined
): asserts value is T {
if (value === null || value === undefined) {
throw new Error('Value must not be null or undefined');
}
}
// 配列要素の存在チェック
function assertArrayHasElements<T>(
arr: T[]
): asserts arr is [T, ...T[]] {
if (arr.length === 0) {
throw new Error('Array must have at least one element');
}
}
実践的な使用例:
typescriptfunction processUserData(userData: unknown) {
// データの存在確認
assertNotNull(userData);
// オブジェクトかどうかの確認
assertIsObject(userData);
// 必要なプロパティの存在と型の確認
assertHasProperty(userData, 'id');
assertIsString(userData.id);
assertHasProperty(userData, 'name');
assertIsString(userData.name);
// この段階で userData は安全に使用可能
console.log(`ユーザーID: ${userData.id}`);
console.log(`ユーザー名: ${userData.name}`);
}
// 補助的なアサーション関数
function assertIsObject(
value: unknown
): asserts value is Record<string, unknown> {
if (typeof value !== 'object' || value === null) {
throw new Error('Expected object');
}
}
function assertHasProperty<
T extends Record<string, unknown>,
K extends string
>(obj: T, prop: K): asserts obj is T & Record<K, unknown> {
if (!(prop in obj)) {
throw new Error(`Expected property '${prop}' to exist`);
}
}
例外処理との組み合わせ
アサーション関数とエラーハンドリングを組み合わせた実践的なパターンを見てみましょう:
typescript// カスタムエラークラス
class ValidationError extends Error {
constructor(
message: string,
public field: string,
public value: unknown
) {
super(message);
this.name = 'ValidationError';
}
}
// 詳細なエラー情報を含むアサーション関数
function assertIsEmail(
value: unknown
): asserts value is string {
if (typeof value !== 'string') {
throw new ValidationError(
`Expected email to be string, got ${typeof value}`,
'email',
value
);
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
throw new ValidationError(
`Invalid email format: ${value}`,
'email',
value
);
}
}
function assertIsAge(
value: unknown
): asserts value is number {
if (typeof value !== 'number') {
throw new ValidationError(
`Expected age to be number, got ${typeof value}`,
'age',
value
);
}
if (value < 0 || value > 150) {
throw new ValidationError(
`Age must be between 0 and 150, got ${value}`,
'age',
value
);
}
}
包括的なバリデーション処理:
typescriptinterface ValidatedUser {
email: string;
age: number;
name: string;
}
function createUser(userData: unknown): ValidatedUser {
try {
assertIsObject(userData);
// email のバリデーション
assertHasProperty(userData, 'email');
assertIsEmail(userData.email);
// age のバリデーション
assertHasProperty(userData, 'age');
assertIsAge(userData.age);
// name のバリデーション
assertHasProperty(userData, 'name');
assertIsString(userData.name);
if (userData.name.trim().length === 0) {
throw new ValidationError(
'Name cannot be empty',
'name',
userData.name
);
}
// 全ての検証が通過した場合、安全に型変換
return {
email: userData.email,
age: userData.age,
name: userData.name.trim(),
};
} catch (error) {
if (error instanceof ValidationError) {
console.error(
`バリデーションエラー in ${error.field}: ${error.message}`
);
}
throw error;
}
}
バリデーションライブラリとの活用
実際の開発では、バリデーションライブラリと組み合わせてアサーション関数を作成することが多いです:
typescript// Zodライブラリを想定した例
import { z } from 'zod';
// スキーマ定義
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150),
roles: z.array(z.enum(['user', 'admin', 'moderator'])),
});
type User = z.infer<typeof UserSchema>;
// Zodスキーマを使ったアサーション関数
function assertIsValidUser(
value: unknown
): asserts value is User {
try {
UserSchema.parse(value);
} catch (error) {
if (error instanceof z.ZodError) {
const details = error.errors
.map(
(err) => `${err.path.join('.')}: ${err.message}`
)
.join(', ');
throw new Error(`User validation failed: ${details}`);
}
throw error;
}
}
API レスポンスの処理での活用:
typescriptasync function fetchUser(userId: string): Promise<User> {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(
`HTTP ${response.status}: ${response.statusText}`
);
}
const data = await response.json();
// レスポンスデータのバリデーション
assertIsValidUser(data);
// この行以降、data は User型として安全に使用可能
return data;
}
// 使用例
async function displayUser(userId: string) {
try {
const user = await fetchUser(userId);
console.log(`ユーザー: ${user.name} (${user.email})`);
console.log(`権限: ${user.roles.join(', ')}`);
} catch (error) {
console.error('ユーザーの取得に失敗:', error.message);
}
}
実際の開発での使い分け
ここまで 4 つの型縮小パターンを詳しく見てきましたが、実際の開発ではどのように使い分けるのが良いでしょうか。それぞれの特徴と適用場面を整理してみましょう。
以下の図で、各パターンの選択指針を確認できます:
mermaidflowchart TD
start["型縮小が必要な状況"] --> check1{"オブジェクトの<br/>プロパティで判定?"};
check1 --|Yes|--> in_op["in オペレータ"];
check1 --|No|--> check2{"クラスの<br/>インスタンス判定?"};
check2 --|Yes|--> instanceofOp["instanceof オペレータ"];
check2 --|No|--> check3{"複雑な条件や<br/>再利用が必要?"};
check3 --|Yes|--> is_guard["is 型ガード"];
check3 --|No|--> check4{"バリデーションと<br/>例外処理が必要?"};
check4 --|Yes|--> assertsOp["asserts アサーション"];
check4 --|No|--> other["その他の手法<br/>(typeof, Array.isArray など)"];
パターン別使い分けガイド
パターン | 適用場面 | メリット | 注意点 |
---|---|---|---|
in オペレータ | Union 型のオブジェクトで特定プロパティの存在で判定 | シンプルで直感的、TypeScript が自動推論 | optional プロパティでは効果薄、文字列リテラル必須 |
instanceof | クラスベースの型判定、継承関係の処理 | 継承チェーンに対応、オブジェクトの出自が明確 | プリミティブ型不可、クラス設計への依存 |
is 型ガード | 複雑な条件、再利用可能な型チェック | 柔軟性が高い、ロジックの再利用可能 | 実装コストが高め、条件の妥当性は自己責任 |
asserts | バリデーション処理、例外的状況での型保証 | 型安全性とランタイムチェックの統合 | 例外処理が必要、パフォーマンスへの配慮要 |
実践的な組み合わせパターン
実際の開発では、複数の型縮小手法を組み合わせて使用することが多いです:
typescript// 複数パターンの組み合わせ例
class ApiService {
// アサーション関数でデータの妥当性を保証
private assertValidResponse(
data: unknown
): asserts data is {
status: string;
result: unknown;
} {
if (typeof data !== 'object' || data === null) {
throw new Error('Invalid response format');
}
if (!('status' in data) || !('result' in data)) {
throw new Error('Missing required response fields');
}
}
// 型ガードで詳細な型判定
private isErrorResponse(response: {
status: string;
result: unknown;
}): response is {
status: 'error';
result: { message: string; code: number };
} {
return (
response.status === 'error' &&
typeof response.result === 'object' &&
response.result !== null &&
'message' in response.result &&
'code' in response.result
);
}
private isSuccessResponse<T>(response: {
status: string;
result: unknown;
}): response is { status: 'success'; result: T } {
return response.status === 'success';
}
async processApiCall<T>(url: string): Promise<T> {
const response = await fetch(url);
const data = await response.json();
// まずアサーション関数で基本構造を確認
this.assertValidResponse(data);
// 次に型ガードで詳細な型判定
if (this.isErrorResponse(data)) {
throw new Error(
`API Error ${data.result.code}: ${data.result.message}`
);
} else if (this.isSuccessResponse<T>(data)) {
return data.result;
} else {
throw new Error('Unexpected response format');
}
}
}
フォーム処理での実践例:
typescript// フォームデータ処理での型縮小活用
interface FormData {
name: string;
email: string;
age?: number;
preferences?: {
newsletter: boolean;
theme: 'light' | 'dark';
};
}
class FormProcessor {
// アサーション関数で必須フィールドをチェック
private assertRequiredFields(
data: unknown
): asserts data is {
name: unknown;
email: unknown;
} {
if (typeof data !== 'object' || data === null) {
throw new Error('Form data must be an object');
}
if (!('name' in data) || !('email' in data)) {
throw new Error('Name and email are required');
}
}
// 型ガードでオプショナルフィールドをチェック
private hasPreferences(
data: any
): data is { preferences: unknown } {
return (
'preferences' in data &&
typeof data.preferences === 'object'
);
}
processForm(rawData: unknown): FormData {
// 必須フィールドの確認
this.assertRequiredFields(rawData);
// 基本フィールドの型チェック
if (
typeof rawData.name !== 'string' ||
typeof rawData.email !== 'string'
) {
throw new Error('Name and email must be strings');
}
const result: FormData = {
name: rawData.name,
email: rawData.email,
};
// in オペレータでオプショナルフィールドをチェック
if (
'age' in rawData &&
typeof rawData.age === 'number'
) {
result.age = rawData.age;
}
// 型ガードでネストしたオブジェクトを処理
if (this.hasPreferences(rawData)) {
const prefs = rawData.preferences;
if (
typeof prefs === 'object' &&
prefs !== null &&
'newsletter' in prefs &&
'theme' in prefs
) {
result.preferences = {
newsletter: Boolean(prefs.newsletter),
theme: prefs.theme === 'dark' ? 'dark' : 'light',
};
}
}
return result;
}
}
パフォーマンスとメンテナンス性の考慮
型縮小パターンを選択する際は、パフォーマンスとメンテナンス性も重要な要素です:
パフォーマンス面の考慮点
in
オペレータとinstanceof
は実行時のコストが低い- 複雑な型ガード関数は条件によってはコストが高くなる
- アサーション関数は例外処理のコストを考慮する必要がある
メンテナンス性の考慮点
- シンプルな条件では
in
やinstanceof
を優先 - 複雑なロジックは型ガード関数にまとめて再利用性を高める
- バリデーション要件が厳しい場面ではアサーション関数が有効
効率的な型縮小の実装例:
typescript// パフォーマンスを意識した型縮小
class OptimizedTypeNarrowing {
// 頻繁に呼ばれる処理では軽量な判定を優先
private fastTypeCheck(
value: unknown
): value is string | number {
const type = typeof value;
return type === 'string' || type === 'number';
}
// 複雑な判定が必要な場合のみ詳細チェック
private detailedTypeCheck(
value: unknown
): value is ComplexType {
// 軽量チェックで早期リターン
if (!this.fastTypeCheck(value)) {
return false;
}
// 必要な場合のみ詳細なチェック
return this.performDetailedValidation(value);
}
private performDetailedValidation(
value: string | number
): value is ComplexType {
// 詳細なバリデーションロジック
// ...
return true; // 簡略化
}
}
interface ComplexType {
// 複雑な型定義
}
まとめ
TypeScript の型縮小は、型安全性と開発体験を大幅に向上させる重要な機能です。本記事で解説した 4 つのパターンを適切に使い分けることで、より堅牢で保守性の高いコードを書くことができますね。
各パターンの要点
in
オペレータ:オブジェクトのプロパティ存在で判定。Union 型の処理に最適instanceof
オペレータ:クラスベースの型判定。継承関係に強いis
型ガード:複雑な条件や再利用可能な型チェック。柔軟性が高いasserts
アサーション:バリデーションと型安全性を統合。例外処理との組み合わせ
実際の開発では、これらの手法を組み合わせて使用することで、型安全性を保ちながら実用的なアプリケーションを構築できます。プロジェクトの要件や処理の複雑さに応じて、最適なパターンを選択していきましょう。
型縮小をマスターすることで、TypeScript の真の力を引き出し、より良いソフトウェア開発体験を得られることでしょう。
関連リンク
- article
TypeScript 型縮小(narrowing)パターン早見表:`in`/`instanceof`/`is`/`asserts`完全対応
- article
TypeScript 共有可能な tsconfig 設計:`tsconfig/bases`で複数パッケージを一括最適化
- article
TypeScript ランタイム検証ライブラリ比較:Zod / Valibot / typia / io-ts の選び方
- article
【解決策】TypeScript TS2307「Cannot find module…」が出る本当の原因と最短復旧手順
- article
Astro × TypeScript:型安全な静的サイト開発入門
- article
Pinia × TypeScript:型安全なストア設計入門
- article
【保存版】Vite 設定オプション早見表:`resolve` / `optimizeDeps` / `build` / `server`
- article
JavaScript Web Workers 実践入門:重い処理を別スレッドへ逃がす最短手順
- article
htmx × Express/Node.js 高速セットアップ:テンプレ・部分テンプレ構成の定石
- article
TypeScript 型縮小(narrowing)パターン早見表:`in`/`instanceof`/`is`/`asserts`完全対応
- article
Homebrew を社内プロキシで使う設定完全ガイド:HTTP(S)_PROXY・証明書・ミラー最適化
- article
Tauri 開発環境の最速構築:Node・Rust・WebView ランタイムの完全セットアップ
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来