#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];