Typescript(構造的型付け)

はじめに

Typescriptのオブジェクトを扱っているとなぜこれはエラーが出てこれはエラーが出ないのか?と思うことがあったのでこの記事に書き残そうと思います。またオブジェクト型については以前記事にしましたので是非御覧ください。
Typescript(オブジェクト型) - 駆け足エンジニアの記録

構造的部分型とは

言葉の定義は、プログラミングのスタイルの一種で、あるオブジェクトが特定のプロパティを持つことだけを重視し、オブジェクトが持っているプロパティが互換しているかどうか、つまり構造が同じかどうかに着目します。

以下コードで確認しましょう。

interface Foo {
  prop1: string;
}
interface Bar {
  prop1: string;
  prop2: number;
}
const obj1: Bar = {
  prop1: "green",
  prop2: 22,
};
const obj2: Foo = obj1;//OK!

型Barをもつobj1という変数に型Fooをもつobj2に代入するとこれはエラーが発生しません。これは、BarはFooと互換性があると見做されるためです。具体的には、prop1: stringというプロパティさえ持っていれば、その型はFooと互換性があると判断されます。
Barはprop1の他にprop2というプロパティを持っているが、これは問題にはなりません。TypeScript の構造的部分型では、必要なプロパティ(Fooの場合はprop1)さえ存在すれば、それ以外のプロパティの有無は問わないためです。

プロパティの過剰が問題にならない一方、プロパティの不足は問題になります。

interface Foo {
  prop1: string;
}
interface Bar {
  prop1: string;
  prop2: number;
}
const obj1: Foo = {
  prop1: "green",
};
const obj2: Bar = obj1;
//obj2でエラー(プロパティ 'prop2' は型 'Foo' にありませんが、型 'Bar' では必須です。)

Freshness

TypeScript の構造的部分型ではあるオブジェクトが特定のプロパティを持つことだけを重視し余分なプロパティは問題にならないことをコードで説明してきましたが、例外があります。その例外の一つが Freshness (厳密なオブジェクトリテラルチェックとも呼ばれます)と呼ばれる挙動で、オブジェクトリテラルに対しては型のチェックが厳しくなり、余分なプロパティが許容されなくなるというものです。
つまり先程の例では一度変数に代入しておいた場合はプロパティの過剰は問題になりませんでしたが、オブジェクトリテラルで変数を定義した場合はエラーとなります。

interface Foo {
  prop1: string;
}
interface Bar {
  prop1: string;
  prop2: number;
}
const obj1: Bar = {
  prop1: "naruto",
  prop2: 12,
};//一度変数に代入しておいた場合

const obj2: Foo = obj1;//OK
const obj3: Foo = {//オブジェクトリテラルで変数を定義した場合
  prop1: "naruto",
  prop2: 17,//エラー
};

これはオブジェクトリテラルを渡す際にわざわざ余分なプロパティを書いていればそれは、勘違いやタイプミスである可能性が高いため、エラーを出すようにしているようです。以下の記事を参考にしました。
typescript-jp.gitbook.io

弱い型(Weak Type)

Weak Typeとは、全てのプロパティがオプションであるオブジェクト型のことを指します。
例えば以下のようなインターフェースです。

interface Foo {
  hoge?: string;
  piyo?: number;
}

さて、構造的部分型で考えるのであれば、この型には必要なプロパティが存在しないので、一度変数に代入しておいた場合は(hogeプロパティ、piyoプロパティを持たない)全てのオブジェクトが代入可能なはずです。ただ以下のようにエラーが発生します。

interface Foo {
  hoge?: string;
  piyo?: number;
}
interface Foga {
  piko: number;
}
const obj1: Foga = {
  piko: 7,
};
const obj2: Foo =obj1; //エラー型 ('Foga' には型 'Foo' と共通のプロパティがありません。)

このエラーはTypescriptのアップデートが進む中で導入されたものだそうです。Weak Typeにおいては、必ずどれか一つのプロパティを持っているオブジェクトしか代入できません。
つまりは以下のようなコードではエラーが出ません。

interface Foo {
  hoge?: string;
  piyo?: number;
}
interface Fuga {
  hoge: string;//追加
  piko: number;
}
const obj1: Fuga = {
  hoge: "konohamaru",
  piko: 7,
};
const obj2: Foo = obj1;//OK!

今回の例では、Fooとの共通プロパティであるhogeの型定義をFugaで行っているため、一度変数に代入しておいた場合はプロパティの過剰は問題になりません。オブジェクトリテラルで変数を定義した場合はFreshness(厳密なオブジェクトリテラルチェック)同様エラーとなります。