🛡️

TypeScript: オプショナルチェイニング vs 型ガード 使い分けガイド

に公開

TypeScriptでオプショナルプロパティを安全に扱う際、オプショナルチェイニング(?.型ガード のどちらを使うべきか迷うことはありませんか?

この記事では、それぞれの特徴と適切な使い分けについて、コード例を交えて解説します。

オプショナルチェイニング(?.)とは

オプショナルチェイニングは、プロパティが存在しない可能性がある場合に安全にアクセスできる演算子です。

基本的な使用例

interface User {
  name: string;
  profile?: {
    age: number;
    email: string;
  };
}

const user: User = {
  name: "Alice"
  // profile は undefined
};

// オプショナルチェイニングを使用
const email = user.profile?.email; // undefined(エラーなし)
const age = user.profile?.age ?? 0; // デフォルト値を設定

ネストしたプロパティへの安全なアクセス

// 深いネストも安全にアクセス
const streetName = user.profile?.address?.street?.name;

// 関数の呼び出しも安全に
user.profile?.updateLastLogin?.();

// 配列要素へのアクセス
const firstHobby = user.profile?.hobbies?.[0];

型ガードとは

型ガードは、条件分岐によって型を絞り込み、特定のスコープ内で型安全性を確保する手法です。

基本的な型ガード

interface User {
  name: string;
  profile?: {
    age: number;
    email: string;
  };
}

const user: User = {
  name: "Alice",
  profile: {
    age: 25,
    email: "alice@example.com"
  }
};

// 型ガードを使用
if (user.profile) {
  // この中では user.profile の型が確定
  console.log(user.profile.email);  // 安全にアクセス
  console.log(user.profile.age);    // 複数回アクセスしても型安全
  
  // 複数の操作を型安全に実行
  const info = `${user.profile.email} (${user.profile.age}歳)`;
}

より複雑な型ガード

// 複数条件での型ガード
if (user.profile && user.profile.age >= 18) {
  console.log("成人ユーザーです");
  console.log(user.profile.email); // 型安全
}

// in演算子を使った型ガード
interface AdminUser extends User {
  adminLevel: number;
}

function processUser(user: User | AdminUser) {
  if ('adminLevel' in user) {
    // user は AdminUser として扱われる
    console.log(`管理者レベル: ${user.adminLevel}`);
  }
}

ユーザー定義型ガード

// カスタム型ガード関数
function hasProfile(user: User): user is User & { profile: NonNullable<User['profile']> } {
  return user.profile !== undefined;
}

if (hasProfile(user)) {
  // user.profile が確実に存在する
  console.log(user.profile.email); // 型安全
  console.log(user.profile.age);   // 型安全
}

実践的な使い分け例

例1: 単発アクセス vs 複数回アクセス

// ❌ 複数回のオプショナルチェイニング(冗長)
const displayName = user.profile?.name || 'Unknown';
const displayAge = user.profile?.age || 0;
const displayEmail = user.profile?.email || 'No email';

// ✅ 型ガードで一度チェック
if (user.profile) {
  const displayName = user.profile.name || 'Unknown';
  const displayAge = user.profile.age || 0;
  const displayEmail = user.profile.email || 'No email';
}

// ✅ 単発アクセスならオプショナルチェイニング
const quickEmail = user.profile?.email ?? 'No email';

例2: API レスポンスの処理

interface ApiResponse {
  data?: {
    users?: User[];
    pagination?: {
      total: number;
      page: number;
    };
  };
}

// オプショナルチェイニング: 単純な値取得
const totalUsers = response.data?.pagination?.total ?? 0;
const firstUser = response.data?.users?.[0];

// 型ガード: 複雑な処理
if (response.data?.users) {
  const users = response.data.users;
  const activeUsers = users.filter(user => user.profile?.age);
  const emailList = users.map(user => user.profile?.email).filter(Boolean);
  
  console.log(`アクティブユーザー数: ${activeUsers.length}`);
  console.log(`メールアドレス一覧:`, emailList);
}

比較表

項目 オプショナルチェイニング(?. 型ガード
適用場面 単発のプロパティアクセス 複数回のプロパティアクセス
記述量 短い やや長い
パフォーマンス 毎回チェック 一度のチェック
型の絞り込み なし あり(スコープ内で型確定)
ネストへの対応 非常に強い 条件が複雑になりがち
可読性 高い(簡潔) 高い(意図が明確)
エラーハンドリング undefined を返す 条件分岐で制御可能
デフォルト値設定 ?? と組み合わせて簡単 if文内で設定

まとめ

オプショナルチェイニングを選ぶべき場合

  • プロパティに一度だけアクセスする
  • 深いネストのオブジェクトにアクセスする
  • 簡潔なコードを書きたい
  • デフォルト値の設定が主目的

型ガードを選ぶべき場合

  • 同じプロパティに複数回アクセスする
  • 複雑な条件でチェックしたい
  • スコープ内で型安全性を確保したい
  • 型の絞り込みが必要

組み合わせて使う

// オプショナルチェイニングで存在チェック、型ガードで詳細処理
if (user.profile?.isActive) {
  // user.profile が存在し、かつ isActive が true
  console.log(user.profile.email);
  console.log(user.profile.age);
}

どちらも強力な機能ですが、目的と文脈に応じて適切に使い分けることで、より読みやすく安全なTypeScriptコードを書くことができます。


この記事が役に立ったら、ぜひスキやコメントで教えてください!

Discussion