我试图强制装饰器的类型参数成为装饰方法返回类型的子集,但我无法解决这个问题:
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。
问题在于您错误地编写了 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();
}
}