web-dev-qa-db-ja.com

TypeScript Generics: 'type is not assignable to type T'

特定のクラスのインスタンスを作成するファクトリを作成しました。ジェネリックスを使用して、返されるすべてのオブジェクトが抽象クラスを拡張するサブクラスからのものであることを確認したいと思います。

以下に示す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メッセージが表示されるのかを理解したいと思います。

ありがとう

8
Joe

関数が常に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