通用TypeScript typeGuard仅具有通用类型参数

时间:2020-03-27 11:19:37

标签: typescript

我想在打字稿中使用以下签名构建通用的typeGuard:

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)之类的东西,以便我可以再次欣赏。

1 个答案:

答案 0 :(得分:0)

您不能在Typescript中编写完全通用的类型保护,因为在运行时没有类型信息。在运行时,您的函数对T一无所知。请参阅如何在typescript playground中编译函数的代码。

只要T到达JS,就意味着您将T用作值,而不是类型,并且得到了您提到的错误:'T' only refers to a type, but is being used as a value here.

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


让我们看看使用curring会发生什么。我们从您提供的两个参数函数开始,但是我将交换参数以进入类型警卫

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

通过指定第一个参数来限制类型。 链接到游乐场here

因此,即使使用curry也无法实现func