强制装饰器的类型参数成为方法返回类型的子集

问题描述 投票:0回答:1

我试图强制装饰器的类型参数成为装饰方法返回类型的子集,但我无法解决这个问题:

function foo<U>() {
    return function<T extends U>(target: any, propertyKey: any, descriptor: TypedPropertyDescriptor<() => T>){};
}

class Bar { 
    a = 1;
}

class Baz {
    @foo<Bar>()        // should be valid but it fails with 1241
    @foo<null>()       // should be valid but it fails with 1241
    @foo<Bar | null>() // should be valid and it is valid
    @foo<number>()     // should fail and it does fail
    qux(): Bar | null {
        return new Bar();
    }
}

我不想更改返回类型,我想这是我的问题。我只想检查

U
是否是
T
的子集。

编辑:我正在使用

experimentalDecorators
,请参阅 https://tsplay.dev/w28pYm

typescript generics
1个回答
0
投票

问题在于您错误地编写了 constraint

T extends U
,这意味着
T
必须是
U
的子类型。但你确实希望它是相反的,其中
T
U
超类型
。如果你可以写
T super U
就好了,但是 TypeScript 不支持这样的 下界 约束。 在 microsoft/TypeScript#14520 上有一个长期开放的功能请求,但到目前为止它还不是该语言的一部分。

幸运的是,至少从调用者的角度来看,您基本上可以模拟这样的约束。这个想法是使用条件类型

T extends U
(这不是你想要的)交换为
U extends T
(这是你想要的)。像这样:

function foo<U>() {
  return function <T extends ([U] extends [T] ? unknown : never)>(
    target: any, propertyKey: any, descriptor: TypedPropertyDescriptor<() => T>) {

  };
}

这就是它的工作原理。如果

U extends T
为 true,则检查
[U] extends [T]
成功,并且对
T
的约束变为
T extends unknown
,它始终为 true,因为
unknown
是 TypeScript 顶级类型)。但是,如果
U extends T
为 false,则检查
[U] extends [T]
失败,并且对
T
的约束变为
T extends never
,这(几乎)总是 false(除非
T
never
但这不太可能意外发生) )因为
never
是 TypeScript 底层类型。

请注意,条件类型是

[U] extends [T] ? unknown : never
而不是
U extends T ? unknown : never
,因为后者将是 分布式条件类型,而这不是您想要的,因为如果
U
union 类型,您不希望检查U
每个成员
T
。用一元组
[
+
]
包裹支票的两侧会关闭分配性,同时保留支票的意义。

现在你得到了你期望的行为:

class Baz {
  @foo<Bar>()        // okay
  @foo<null>()       // okay
  @foo<Bar | null>() // okay
  @foo<number>()     // error
  qux(): Bar | null {
    return new Bar();
  }
}

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.