Параметр типа расширение типа объединения не сужается

#typescript #typescript-generics

Вопрос:

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

У меня есть следующие определения типов:

 interface typeMap { // for mapping from strings to types
  string  : string;
  number  : number;
  boolean : boolean;
}



type Constructor<T = unknown> = { new (...args: any[]): T };

type ConstructedType<T extends Constructor> = T extends { new(...args: any[]): infer U; }
? U: never;

type PrimitiveOrConstructor = // 'string' | 'number' | 'boolean' | constructor
  | Constructor
  | keyof typeMap;
 

Теперь я ожидал бы, что смогу сузить T extends PrimitiveOrConstructor круг, но в итоге это не сработает:

 // infer the guarded type from a specific case of PrimitiveOrConstructor
type GuardedType<T extends PrimitiveOrConstructor> =
  T extends Constructor
    ? ConstructedType<T>
    : T extends keyof typeMap
      ? typeMap[T]
      : never;

function typeGuard<T extends PrimitiveOrConstructor>(o: unknown, className: T): o is GuardedType<T> {

  const localPrimitiveOrConstructor: PrimitiveOrConstructor = className;

  if (typeof localPrimitiveOrConstructor === 'string') {
    return typeof o === localPrimitiveOrConstructor;
  }

  return o instanceof localPrimitiveOrConstructor;

}
 

Если я заменю localPrimitiveOrConstructor его на className тип T of, он не сузится. Даже если я определяю условие как защиту типа, внутри блока if className оно сужено до T amp; keyof typeMap , а за его пределами оно все еще T не сужено Constructor .

Есть ли что-то неправильное в реализации, из-за чего существующее исправление здесь не применяется?

Комментарии:

1. Как вы используете typeGuard ? Я имею в виду, какие аргументы вы используете с этой функцией

2. Вы можете увидеть некоторые примеры здесь , например: expect(typeGuard(5, 'number')).toBe(true) expect(typeGuard(new B(), B)).toBe(true) (Для некоторых class B {} )

Ответ №1:

Просто не используйте универсальные:

 interface typeMap { // for mapping from strings to types
    string: string;
    number: number;
    boolean: boolean;
}

type Constructor<T = unknown> = { new(...args: any[]): T };

type ConstructedType<T extends Constructor> = T extends { new(...args: any[]): infer U; }
    ? U : never;

type PrimitiveOrConstructor = 
    | Constructor
    | keyof typeMap;

function typeGuard(o: unknown, className: PrimitiveOrConstructor) {

    if (typeof className === 'string') {
        const check = className // keyof typeMap
        return typeof o === className;
    }

    const check = className // Constructor<unknown>

    return o instanceof className;

}

 

Игровая площадка

Комментарии:

1. Это действительно отвечает на мой вопрос, как я его задал, однако я немного упростил его, потому что думал, что это не typeGuard будет иметь значения — он должен быть общим, потому что он предназначен для «извлечения», какой тип создается «именем класса». Я отредактирую свой вопрос.

2. В итоге я использовал это решение, но добавил две общие перегрузки, чтобы сохранить расширенную функциональность. Если вы можете добавить эти строки, я могу принять этот ответ. function typeGuard<T extends Constructor>(o: unknown, className: T): o is ConstructedType<T>; function typeGuard<T extends keyof typeMap>(o: unknown, className: T): o is typeMap[T];