TypeScript Generics: 'тип не может быть назначен типу T'

Я создал фабрику, которая будет создавать экземпляры определенных классов. Я хочу использовать дженерики, чтобы гарантировать, что все возвращаемые объекты принадлежат подклассам, которые расширяют абстрактные классы.

Я подумал, что логику метода createInstance, показанного ниже, можно описать как «createInstance () вернет тип T, который ограничен как класс, расширяющий Animal.

Как видите, 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, которые:

При создании фабрик в TypeScript с использованием универсальных шаблонов необходимо ссылаться на типы классов с помощью их функций-конструкторов. Например,

function create<T>(c: {new(): T; }): T {
    return new c();
}

но я действительно не хочу передавать конструктор класса в функцию, если это возможно, и хотел бы понять, почему я вообще получаю сообщение not assignable to type T.

Спасибо


person Joe    schedule 10.04.2018    source источник


Ответы (1)


Если ваша функция всегда возвращает 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
person Titian Cernicova-Dragomir    schedule 10.04.2018
comment
Спасибо за ваш ответ. Плохой пример с моей стороны. Я имел в виду возможность выбора классов для возврата в зависимости от переключателя. Позвольте мне изменить пример. - person Joe; 10.04.2018
comment
A true generic function would return a value that honors the generic parameter. Итак, теперь, когда я изменил свой пример и у меня есть 2 варианта в методе createInstance(), не является ли мой класс универсальным, поскольку он всегда должен возвращать класс, расширяющий Animal? Я не хочу, чтобы он просто возвращал Animal, потому что я могу использовать другие методы и свойства, специфичные для подкласса. Вместо этого я просто хочу знать, что возвращаемое значение является Animal, чтобы я мог быть уверен, что у меня есть метод makeSound(), но я хочу, чтобы этот фактический экземпляр подкласса. - person Joe; 10.04.2018
comment
@Joe Это все еще не универсальный, потому что он не работает ни для каких T только ограниченное количество классов, производных от Animal. Вы должны рассмотреть вариант перегрузки, это именно то, что вы ищете, он возвращает определенный тип животного в зависимости от значения параметра. - person Titian Cernicova-Dragomir; 10.04.2018
comment
Спасибо Тициану. Это очень полезно - person Joe; 10.04.2018