Back to Portfolio
June 28, 2026

Discriminated Unions(判別可能なユニオン型)

技術メモ

🔗 Reference

概要

型の推論を行うための共通のプロパティを定義したユニオン型。
以下は識別子となるtype"credit"なのか"bank"なのかでdataの型を分岐させている。

// クレジットカード
type CreditCard = {
  code: number;
  name: string;
  cvc: number;
  expiration_month: number;
  expiration_year: number;
};
// 銀行口座
type Bank = { account: number; bank_code: number; branch_code: number };

// typeフィールドがDiscriminated Unionsにおける識別子として機能する
type Payment = { type: 'card'; data: CreditCard } | { type: 'bank'; data: Bank };

const handlePayment = (paymentData: Payment) => {
  if (paymentData.type === 'card') {
    // ここではdataはCreditCard型として推論される
    console.log('💳 I am CreditCard type!', paymentData.data);
  } else if (paymentData.type === 'bank') {
    // ここではdataはBank型として推論される
    console.log('🏦 I am Bank type!', paymentData.data);
  }
};

判別のための識別子は複数のフィールドで構成することも可能。
以下はtypeで大分類、subtypeで中分類を用意して複合的に型を分岐させている例。

type MultiDiscriminatedUnion =
  | { type: 'credit'; subtype: 'master'; data: string }
  | { type: 'credit'; subtype: 'visa'; data: number }
  | { type: 'bank'; subtype: 'yu_cho'; data: boolean }
  | { type: 'bank'; subtype: 'other'; data: string[] };

const Test = (option: MultiDiscriminatedUnion) => {
  if (option.type === 'credit') {
    if (option.subtype === 'master') {
      // data は string型として判断
      console.log('I am MasterCard!', option.data);
    } else if (option.subtype === 'visa') {
      // data は number型として判断
      console.log('I am VisaCard!', option.data);
    }
  } else if (option.type === 'bank' && option.subtype === 'yu_cho') {
    // data は boolean型として判断
    console.log('I am ゆうちょ銀行!', option.data);
  }
};

識別子として使える型

  • 文字列リテラル型("success""error"など)
  • 数値リテラル型(1, 100など)
  • 論理値リテラル型(true or false
  • null
  • undefined
type DiscriminatedUnion =
  | { type: '文字列'; data: string }
  | { type: 1; data: number }
  | { type: true; data: boolean }
  | { type: null; data: string[] }
  | { type: undefined; data: number[] };

const Test = (option: DiscriminatedUnion) => {
  if (option.type === '文字列') {
    console.log(option.data); // data: string
  } else if (option.type === 1) {
    console.log(option.data); // data: number
  } else if (option.type === true) {
    console.log(option.data); // data: boolean
  } else if (option.type === null) {
    console.log(option.data); // data: string[]
  } else if (option.type === undefined) {
    console.log(option.data); // data: number[]
  }
};

ZodでのDiscriminated Unions

Discriminated unions | zod.dev

TypeScriptのDiscriminated Unionsと同じ感覚でフォームの入力状態に合わせてスキーマを定義できる。

const Payment = z.discriminatedUnion('payment_type', [
  z.object({
    payment_type: z.literal('credit'),
    code: z.string(),
    // ....
  }),
  z.object({
    payment_type: z.literal('bank'),
    bank_code: z.string(),
    // ...
  }),
]);

name="payment_type"なラジオボタンのvalueを定義して、そのvalueがcreditなのかbankなのかでスキーマを判別できるようにするみたいなことが可能。 zod-form-validateを活用すれば決済方法に応じて適切にバリデーションを行うこともできる。