Использование InstanceType <T>с заводской функцией - PullRequest
0 голосов
/ 25 октября 2019

У меня есть довольно простая фабричная функция, которая использует карту конструкторов для определения правильного типа объекта для создания на основе переданного строкового параметра.

Мне известно о фабричном шаблоне, описанном в примерахкоторый передает конструктор класса на фабрику, но в моем случае мне нужно передать простую строку.

class Vehicle {
    public wheels: number;
}
class Car extends Vehicle {
    public drive: number
}
class Bike extends Vehicle {
    public ride: number;
}

const CTORS = {
    car: Car,
    bike: Bike
}

type VehicleTypes = typeof CTORS;

function factory<T extends keyof VehicleTypes>(name: T): InstanceType<VehicleTypes[T]> {
    let ctor: VehicleTypes[T] = CTORS[name];

    // un-comment to see error    
    // return new ctor();

    return new ctor() as InstanceType<VehicleTypes[T]>;
}

let abc = factory('bike');
abc.ride = 5;   // type checks ok

Вышеприведенный код работает, и проверки типов выполняются нормально, но явной типизации при возврате необходимо избегатьошибка компилятора:

(Type 'Car | Bike' is not assignable to type 'InstanceType<{ car: typeof Car; bike: typeof Bike; }[T]>'.  Type 'Car' is not assignable to type 'InstanceType<{ car: typeof Car; bike: typeof Bike; }[T])

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

type ReturnTypes = { 
    [P in keyof VehicleTypes]: InstanceType<VehicleTypes[P]>
}
function factory<T extends keyof VehicleTypes>(name: T): ReturnTypes[T] {
    let ctor = CTORS[name];
    return new ctor();
}

1 Ответ

1 голос
/ 25 октября 2019

В настоящее время это ограничение TypeScript: неразрешенные условные типы, то есть те, которые зависят от еще не определенного параметра универсального типа, непрозрачны для компилятора;он действительно не может видеть, что ему присваивается какое-то значение. Тип InstanceType<T> является определенным следующим образом:

type InstanceType<T extends new (...args: any) => any> = 
  T extends new (...args: any) => infer R ? R : any;

Это условный тип, а внутри реализация factory(), тип InstanceType<VehicleTypes[T]>не разрешен, так как T не указан.


Существует несколько открытых проблем GitHub с предложениями по упрощению работы с такими неразрешенными условными типами, но в настоящее время ни один из них не реализован (по состоянию на TS3.7). Вот несколько ссылок на них, если вы достаточно внимательны, чтобы пойти туда и дать им ? или иным образом отстаивать их:

  • microsoft / TypeScript # 13995 : параметры универсального типа должны бытьреализации суженных внутренних функций
  • microsoft / TypeScript # 23132 : разрешить ограниченные обобщенные значения для разрешения условных типов ранее
  • microsoft / TypeScript # 27808 : разрешить обобщенныепараметры типа ограничены типами единиц, чтобы анализ потока управления мог сузить параметры универсального типа
  • microsoft / TypeScript # 33912 : анализ потока управления должен использоваться для получения условных возвращаемых типов

Сейчас я бы сказал, что либо используйте утверждение типа, как вы, или найдите способ представления вашего типа способом, который не зависит от условных типов. Один из возможных путей продвижения вперед заключается в том, чтобы заметить, что конструкторам классов в TypeScript присваивается свойство prototype того же типа, что и их тип экземпляра. Это немного странно и не совсем правильно, так как у фактического прототипа не будет никаких свойств только для экземпляра, но так оно и есть, и вряд ли изменится .

Таким образом, вы можете сделать свой собственный InstanceType, который использует этот факт:

type MyInstanceType<T extends { prototype: any }> = T['prototype'];

И затем вы можете написать

function factory<T extends keyof VehicleTypes>(name: T): MyInstanceType<VehicleTypes[T]> {
    let ctor: VehicleTypes[T] = CTORS[name];
    return new ctor();
}

без ошибок, и вы все равно получите проверку типаожидайте:

let abc = factory('bike');
abc.ride = 5;   // type checks ok

Надеюсь, это поможет;удачи!

Ссылка на код

...