TypeScript ではクラス以外に関数でもジェネリクスが使えるようで、それも呼び出し時に型を指定しなくても引数の型から推論してくれるような挙動をするようで、そこでふと、継承関係にある複数のインタフェースを引数に渡した場合、どういう挙動になるのか気になったので検証してみました。
O < A < B < C
の順に継承関係にあるインタフェースを定義して、可変長引数でオブジェクトを受け取りマージして返す Object.assign
のラッパー関数に渡して、その返り値から TypeScript がどういう型として評価されたかを確認します。
感覚的には最も近い共通祖先として評価してもらえると嬉しいところです。
interface O {}
interface A extends O { a: string; };
interface B extends A { b: string; };
interface C extends B { c: string; };
const o: O = {};
const a: A = { a: 'a' };
const b: B = { a: 'a', b: 'b' };
const c: C = { a: 'a', b: 'b', c: 'c' };
function assign<T>(...v: T[]) {
return Object.assign({}, ...v) as T;
}
assign(o, a, b, c).c;
// -> error TS2339: Property 'c' does not exist on type 'O'.
assign(a, b, c).c;
// -> error TS2339: Property 'c' does not exist on type 'A'.
assign(b, c).c;
// -> error TS2339: Property 'c' does not exist on type 'B'.
assign(c).c;
// -> no error
// 並べ替えて試してみる
assign(c, b, a, o).c;
// -> error TS2339: Property 'c' does not exist on type 'O'. (同様)
エラーメッセージを見るに、A
と B
と C
の組なら A
と評価され、 B
と C
の組なら B
と評価されるので、希望通り、並び順に関係なく最も近い共通祖先まで辿って評価してくれるようですね。