仅具有泛型类型参数的泛型 TypeScript 类型保护

Generic TypeScript type guards with only generic type parameter

我想在 TypeScript 中构建一个具有以下签名的通用类型保护:

declare function typeGuard<T>(obj: any): o is T;

我找到了一些文章(例如 this),这些文章用这样的签名解决了这个问题:

declare function typeGuard<T>(obj: any, clazz: T): o is T;

但这需要通过代码携带类型信息,所以优先使用第一个签名。

第二种情况的解决方案如下:

type typeMap = {
  string: string;
  number: number;
  boolean: boolean;
}

type PrimitiveOrConstructor =
  | string
  | { new (...args: any[]): any }
  | keyof typeMap;

type GuardedType<T extends PrimitiveOrConstructor> = T extends { new(...args: any[]): infer U; } ? U : T extends keyof typeMap ? typeMap[T] : never;

function typeGuard<T extends PrimitiveOrConstructor>(o: any, className: T): o is GuardedType<T> {
  const localPrimitiveOrConstructor: PrimitiveOrConstructor = className;
  if (typeof localPrimitiveOrConstructor === 'string') {
    return typeof o === localPrimitiveOrConstructor;
  }
  return o == localPrimitiveOrConstructor || (typeof localPrimitiveOrConstructor === 'object' && o instanceof localPrimitiveOrConstructor);
}

这导致:

typeGuard(2, 'number'); // true
typeGuard('foobar', 'string'); // true
typeGuard(new Foobar(), Foobar); // true

但是如果我处于泛型类型上下文中怎么办,例如像这样的函数:

declare function <T>func(arg: T);

在这种情况下,无法做到:

function <T>func(arg: any) {
    if (typeGuard(arg, T)) { ... } // throws error: T is used as a value

所以在这种情况下,我更喜欢这样的东西:

function <T>func(arg: any) {
    if (typeGuard<T>(arg)) { ... }

我最近阅读了 this 文章并试图想出这样的东西(这当然行不通):

type Check<X, Y> = X extends Y ? true : false;
declare function check<C, T>(o: T): Check<T, C>;

然后像这样使用它:

function <T>func(arg: any) {
    if (check<T>(arg)) { ... }

PS: 可以肯定的是,我不想将包装函数的签名更改为这样的东西 function <T>func(arg: any, clazz: T) 这样我就可以再次加入了。

你不能在 typescript 中编写一个完全通用的类型保护,因为在运行时没有类型信息。在运行时,您的函数无法了解有关 T 的任何信息。请参阅 typescript playground.

中如何转译您的函数代码

每当 T 到达 JS 时,这意味着您使用 T 作为值,而不是类型,并且您会收到您提到的错误:'T' only refers to a type, but is being used as a value here.

并且生成的 JS 代码不是类型安全的。


让我们看看柯里化会发生什么。我们从您提供的两个参数函数开始,但我将交换参数,以获取类型保护

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

这个函数的柯里化版本是:

function curriedTypeGuard<T extends PrimitiveOrConstructor>(className: T): (o: any) => o is GuardedType<T>

这就像一个工厂,用于更专业的单参数类型保护。

const stringSpecializedTypeGuard = curriedTypeGuard("string") // (o: any) => o is string
const classSpecializedTypeGuard = curriedTypeGuard(Class); // (o: any) => o is Class

通过指定第一个参数来限制类型。 Link 去游乐场 here

所以即使使用柯里化,您也无法实现您的 func