TypeScript | 基礎文法:Union・基本型操作 – union型の共通プロパティ

TypeScript
スポンサーリンク

「union型の共通プロパティ」とは何か

まず前提から整理します。
union型は「A か B かどちらか」の型でした。

type User = {
  id: number;
  name: string;
};

type Admin = {
  id: number;
  name: string;
  permissions: string[];
};

type Person = User | Admin;
TypeScript

PersonUserAdmin のどちらかです。
ここで重要なのが 「共通しているプロパティ」と「片方にしかないプロパティ」 の違いです。

idname は両方にある「共通プロパティ」。
permissionsAdmin にしかない「片方だけのプロパティ」。

TypeScript は、この違いをちゃんと理解していて、

共通プロパティ → そのまま安全にアクセスできる
片方だけのプロパティ → そのままでは危険なので、型ガードが必要

というルールで動きます。

ここから、具体例でこの感覚を固めていきます。


共通プロパティは「そのまま使える」

union型全体に“必ず存在する”ものは安全

さきほどの Person 型を使います。

type User = {
  id: number;
  name: string;
};

type Admin = {
  id: number;
  name: string;
  permissions: string[];
};

type Person = User | Admin;
TypeScript

このとき、次の関数はOKです。

function printBasicInfo(person: Person) {
  console.log(person.id);   // OK
  console.log(person.name); // OK
}
TypeScript

なぜかというと、

User にも Admin にも idname がある
Person のどちらのパターンでも、そのプロパティは必ず存在する
→ だから union 型のままでも安全にアクセスできる

からです。

ここでのポイントは、「TypeScript は union の“全メンバーに共通して存在するプロパティ”だけを、安全なものとして認める」ということです。

逆に言うと、「どのケースでも必ずある」ことが保証されているものだけ、何も考えずに使える、ということですね。


片方だけのプロパティは「そのまま使えない」

型ガードなしでアクセスすると怒られる

同じ Person を使って、permissions にアクセスしようとします。

function printPermissions(person: Person) {
  console.log(person.permissions); 
  //           ~~~~~~~~~~~~~~~~~
  // Property 'permissions' does not exist on type 'User'.
}
TypeScript

エラーの意味を言い換えると、

Person の中には User も含まれているけど、User には permissions はないよ」
「だから、Person としか分かっていない状態で permissions を触るのは危ない」

ということです。

つまり、「union 型のままでは、共通プロパティ以外には触らせない」
これが TypeScript の基本姿勢です。


共通プロパティと型ガードの関係

共通プロパティでできること/できないこと

共通プロパティは、そのまま使えます。
しかし「共通しているのは名前だけで、中身の型が違う」場合は注意が必要です。

type A = {
  value: string;
};

type B = {
  value: number;
};

type AB = A | B;

function printValue(ab: AB) {
  // ab.value.toUpperCase(); // エラー
}
TypeScript

value というプロパティ名は共通していますが、

A では string
B では number

です。

このとき、ab.value の型は string | number になります。
だから、toUpperCase(string 専用メソッド)は直接は呼べません。

ここでやれるのは、

共通のメソッドだけ使う(toString など)
型ガードで中身の型を絞ってから使う

のどちらかです。

function printValue(ab: AB) {
  if (typeof ab.value === "string") {
    console.log(ab.value.toUpperCase()); // ここでは string
  } else {
    console.log(ab.value.toFixed(2));    // ここでは number
  }
}
TypeScript

この例から分かる大事なポイントは、
「共通プロパティ」というのは“名前が同じ”だけでなく、“型も共通”である必要がある、ということです。


共通プロパティをうまく設計すると、扱いやすいunion型になる

「本当に共通しているもの」を1つの型として切り出す

さきほどの UserAdmin は、idname が完全に共通していました。
こういう場合、「共通部分だけを別の型に切り出す」とコードがかなり読みやすくなります。

type BasePerson = {
  id: number;
  name: string;
};

type User = BasePerson & {
  // 追加情報があればここに書く
};

type Admin = BasePerson & {
  permissions: string[];
};

type Person = User | Admin;
TypeScript

こうしておくと、

Person はみんな BasePerson の性質を持っている」
「Admin だけが extra として permissions を持っている」

という構造が、型の定義そのものから読み取れるようになります。

そうすると、

共通処理(名前やIDを表示するなど)は BasePerson 前提で書く
role ごとの処理は、型ガードで Admin / User に分けて書く

という整理されたコードを作りやすくなります。

共通プロパティを意識して型を設計すると、“絶対にあるもの”と“あるかないかが変わるもの”の境界が明確になる、というのが大きなメリットです。


実践的なイメージ:共通プロパティだけを見る関数/分岐で使い分ける

共通プロパティだけを扱う汎用関数

例えば、「誰であれ id と name さえあれば表示できる」関数は、共通プロパティ前提で書けます。

function printNameAndId(person: Person) {
  console.log(person.id, person.name); // 共通プロパティだけ
}
TypeScript

この関数は、User でも Admin でも、安全に呼べます。
「共通部分だけを観る関数」だからです。

特定の型だけが持つプロパティは、型ガードとセットで

一方で「権限を持つ人だけ特別扱いしたい」処理では、共通プロパティに頼れません。
permissionsAdmin にしかないからです。

function printIfAdmin(person: Person) {
  if ("permissions" in person) {
    // ここから先は Admin の世界
    console.log(person.name, person.permissions);
  } else {
    console.log(person.name, "(一般ユーザー)");
  }
}
TypeScript

このように、

共通プロパティだけを見る関数
型ガードで分岐して、特定の型だけのプロパティを見る関数

を使い分けていくと、union 型の扱いがぐっと整理されます。


まとめ:union型の共通プロパティをどう捉えるか

最後に、感覚だけまとめます。

union 型では、「全てのパターンに必ず存在するプロパティ」だけが、
何の条件分岐もなしに安全に触れる領域になります。

それ以外のプロパティは、

共通でなかったり
型が違っていたり
特定のパターンだけに存在したり

するので、「まずは if / 型ガードで“今どのパターンか”を確かめてから触るもの」になります。

だから、型を設計するときはいつも、

この union に共通して必ず存在する情報は何か?
逆に、パターンごとに変わる情報はどれか?

を意識してみてください。

共通プロパティを見極めてあげると、
union 型は単なる「ごちゃっとした複数パターン」ではなく、
「共通部分+役割ごとの差分」という、きれいな構造を持った表現に変わっていきます。

タイトルとURLをコピーしました