Typescript(合併型と交差型)

はじめに

今回は型指定で度々登場する合併型(union型)と交差型(intersection型)について取り上げます。この2つは高校数学で習うベン図を使うことで、違いが分かります。2つの型が存在したとして、その2つの合わせた部分を型の集合とした合併型、2つの共通部分のみを方の集合とした交差型というイメージです。

f:id:Tsuboi99553758:20200831092545p:plain
合併型
f:id:Tsuboi99553758:20200831092609p:plain
交差型

型の定義

それでは実際にコードで確認しましょう。

function combine(input1: number, input2: number) {
  let result = input1 + input2;
  return result;
}
console.log(combine(1, 2)); //3

まず関数型について軽く解説します。ここではcombineという関数があり、仮引数では型指定によってnumber型を持つinput1,input2が存在します。仮引数は型指定で型を定義する必要がありますが、返り値は型推論によりTypescriptが推論してくれています。実際、return resultと返り値を指定したタイミングでconbineにマウスホバーしてみるとfunction combine(input1: number, input2: number): numberと最後の:numberが返り値の型となっています。
話を合併型、交差型に戻します。今回、実引数には(1,2)とnumber型を渡しています。この実引数にstring型を渡したい場合はどう改善すべきでしょう?
当然、実引数にstring型を入れるだけではTypescriptによりエラーが発生します。

function combine(input1: number, input2: number) {
  let result = input1 + input2;
  return result;
}
console.log(combine('uzumaki','naruto')); //エラー(型 '"uzumaki"' の引数を型 'number' のパラメーターに割り当てることはできません。)

ここで登場するのが、合併型(union型)です。

function combine(input1: number | string, input2: number | string) {
  let result = input1 + input2; //エラー!
  return result;
}
console.log(combine("uzumaki", "naruto"));

合併型の導入は簡単です。仮引数にnumber | stringと|で分けるだけです。これによりinput1,input2にはnumberまたはstringどちらかの型を持つということをTypescriptが理解しています。しかし他の箇所でエラーが発生しています。なぜならinput1とinput2をプラスの演算子で結合したときに、正しい結果になることがTypescriptは理解できていないからです。このエラーはランタイム上でデータ型をチェックすることによって解消することができます。それはtypeofキーワードを使います。

function combine(input1: number | string, input2: number | string) {
  let result;
  if (typeof input1 === "number" && typeof input2 === "number") {
    result = input1 + input2;
  } else {
    result = input1.toString() + input2.toString();
  }
  return result;
}
console.log(combine(1, 2)); //3

console.log(combine("uzumaki", "naruto")); //uzumakinaruto

このような手順を踏むことによって、合併型を用いたコードは正常に機能します。

エイリアス(タイプエイリアス)

交差型を説明するために型エイリアスを取り上げます。タイプエイリアスを直訳すると型の別名の意です。
例えば次のようなものです。

type Person = {
  name: string,
  age:number
}

ここではオブジェクトの型エイリアスを定義しています。nameとageという2つのプロパティがありその型をそれぞれ定義しています。重要なことは型エイリアス初期値を与えることはできないことです。あくまで型の指定だけです。
用途はシンプルで、型指定時に型エイリアス名を記述するだけです。

type Person = {
  name: string;
  age: number;
};
const shinobi: Person = {
  name: "naruto",
  age: 12,
};

このように一つの型エイリアスを作ることによって、何度も型指定時に使いまわすことが可能となります。

エイリアスによる交差型

さて簡単な型エイリアスの説明が終わったところで、交差型の説明に入ります。先に例を見てみましょう。

type Hoge = {
  foo: string;
  bar: number;
};
type Piyo = {
  foo: string;
  baz: boolean;
};
type HogePoyo = Hoge & Piyo;

const obj: HogePoyo = {
  foo: "foooooooo",
  bar: 3,
  baz: true,
};

この例では、HogePiyoというのはHogeでもありPiyoでもある型を表します。これが交差型です。ここではHogeとPiyoというエイリアス名を&で挟むことで交差型を表しています。
エイリアスに合併型を利用することも可能です。

type Hoge = {
  foo: string;
  bar: number;
};
type Piyo = {
  foo: string;
  baz: boolean;
};
type HogePoyo = Hoge | Piyo;

const obj1: HogePoyo = {
  foo: "foooooooo",
  bar: 3,
};
const obj2: HogePoyo = {
  foo: "foooooooo",
  baz: true,
};
const obj3: HogePoyo = {
  foo: "foooooooo",
  bar: 3,
  baz: true,
};

重要なことは、合併型を持つ値は必ずしも合併のどちらか一つのメンバーであるとは限りません(ここではobj1とobj2がどちらか一つのメンバー)。実際には、同時に両方のメンバーになれます。(obj3のように)