特定のクラスのインスタンスを作成するファクトリを作成しました。ジェネリックスを使用して、返されるすべてのオブジェクトが抽象クラスを拡張するサブクラスからのものであることを確認したいと思います。
以下に示すcreateInstance
メソッドのロジックは、 'createInstance()が、Animalを拡張するクラスに制限されている型T
を返すと説明できると思いました。
ご覧のとおり、LionはAnimalを拡張していますが、コンパイラの警告type Lion is not assignable to type T
が引き続き表示されます。
abstract class Animal {
abstract makeSound(): void;
}
class Bear extends Animal {
public makeSound() {
console.log('growl');
}
}
class Lion extends Animal {
public makeSound() {
console.log('roar');
}
}
function createInstance<T extends Animal>(type: string): T {
switch(type) {
case 'bear':
return new Bear(); // 'type Bear is not assignable to type T'
case 'lion':
return new Lion(); // 'type Lion is not assignable to type T'
}
}
createInstance().makeSound();
私は TypeScript Generics docsの最後に次のことを読みました:
TypeScriptでジェネリックスを使用してファクトリを作成する場合、コンストラクター関数でクラス型を参照する必要があります。例えば、
function create<T>(c: {new(): T; }): T { return new c(); }
しかし、可能であればクラスコンストラクターを関数に渡す必要はありません。なぜ最初にnot assignable to type T
メッセージが表示されるのかを理解したいと思います。
ありがとう
関数が常にLion
を返す場合、その結果の型は実際には一般的ではありません。たとえば、create<Tiger>()
と記述しても、関数はLion
を返します。真のジェネリック関数は、ジェネリックパラメーターを尊重する値を返します。
あなたが発見したように、引数としてコンストラクタを渡すことができます:
function create<T>(c: {new(): T; }): T {
return new c();
}
または、関数をジェネリックにせず、Animal
またはLion
を返すようにすることもできます。戻り値の型を決定する引数値に基づくロジックがある場合、より多くのオーバーロードが発生する可能性があります。
// Public signatures, we tie function parameter values to return value for specific types
function createInstance(type: "Lion"): Lion
function createInstance(type: "Tiger"): Tiger
// Private signature, not visible from outside
function createInstance(type: "Lion" | "Tiger"): Animal {
if(type === "Lion") {
return new Lion();
}
else if(type === "Tiger") {
return new Tiger();
}
}
let tiger = createInstance("Tiger"); // will be typed as Tiger
let lion = createInstance("Lion");// will be typed as Lion
let err = createInstance("Lama");// will be an error since the function does not know how to create a Lama