TypeScriptのinterface拡張に関するちょっとしたメモ書きです。
基本
以下のような定義のinterfaceがあったとします。(例えばライブラリなどで宣言されていたり)
型定義.d.ts
interface iFoo {
bar: string
baz: number|string
}
この定義は自分(のアプリケーションコード内)で勝手にプロパティを追加宣言して拡張することができます。
my-app.ts
// iFooに新しくquxプロパティを定義
interface iFoo {
qux: boolean
}
// 3種のプロパティが揃ってないとエラーになる
const foo: iFoo = {
bar: "bar",
baz: 0,
qux: true,
}
これは Declaration Merging と言うそう。
しかし制限もあり、元々あるプロパティの型を上書きすることはできません。
// barプロパティはstring型として定義済みなのでNG
interface iFoo {
bar: number // Error!
}
ただしextendすれば条件付きで変えることは可能です。
例えばbaz
プロパティ(number|string
型)を変更する場合、
-
any
型のように元の型にも適合するような緩い型にする -
number
型のように受け取れる型を狭める
形での拡張はできます。
// number|string => any: OK
interface iFooExtend extends iFoo {
baz: any
}
// number|string => number:これもOK
interface iFooExtend extends iFoo {
baz: number
}
しかし以下のように拡張しようとすると怒られます。
interface iFooExtend extends iFoo {
baz: number|string|boolean // Error!
}
これはnumber|string
型にはboolean型が適合しない(代入できない)ためです。
要は元の型に代入できるような型であることが条件になっています。
どうしても違法拡張したいとき
いったん定義を弱める拡張を行ってから、再度拡張するのは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拡張を頻繁に行いたいときは便利そうです。