TypeScriptのinterface拡張に関するちょっとしたメモ書きです。
v3.9.2で検証しています。(おそらく2.x, 3.x系いずれも動くと思われます)
基本
以下のような定義のinterfaceがあったとします。
interface iFoo {
bar: string
baz: number|string
}
この定義は自分で後からプロパティを追加して拡張することができます。
// iFooに新しくquxプロパティを定義
interface iFoo {
qux: boolean
}
// 3種のプロパティが無いとエラーになる
const foo: iFoo = {
bar: "bar",
baz: 0,
qux: true,
}
これは Declaration Merging と言うそう。
しかし元々あるプロパティの型を上書きすることはできません。
// barは定義済みなのでNG
interface iFoo {
bar: number
}
ただしextendすれば条件付きで変えることは可能です。
any
型のように引き続きnumber|string
型に適合するような形に変えたり、number|string
=>number
のように受け取れる型を狭める形での拡張はできます。
// number|string => any: OK
interface iFooExtend extends iFoo {
baz: any
}
// number|string => number:これもOK
interface iFooExtend extends iFoo {
baz: number
}
しかし以下のように拡張しようとすると怒られます。
// NG!
interface iFooExtend extends iFoo {
baz: number|string|boolean
}
number|string|boolean
型はnumber|string
型に適合しない(代入できない)ためとかなんとか。
要は元の型(number|string
型)に代入できるような型であることが条件のようです。
どうしても違法拡張したいとき
いったん定義を弱める拡張を行ってから、再度拡張するのはOKらしいです。
interface iFooWeaken extends iFoo {
baz: any
}
interface iFooExtend extends iFooWeaken {
baz: number|string|boolean
}
さらにWeakenという型を作って経由することで記述を簡略化することができます。
(コードは**こちら**から引用)
type Weaken<T, K extends keyof T> = {
[P in keyof T]: P extends K ? any : T[P];
};
interface iFooExtend extends Weaken<iFoo, 'baz'> {
baz: number|string|boolean;
}
やや邪道感が漂いますが、interface拡張を頻繁に行いたいときは便利そうです。