- bounty将于明天过期**。此问题的答案可获得+100的声誉奖励。Krzysztof Kaczyński正在寻找来自声誉良好的来源的答案:我不仅对如何解决这个问题感兴趣,而且还想了解TS为什么要以这种方式解决这个条件类型
我想知道如果我添加了一个应该保证期望类型的约束,为什么TypeScript会将条件类型解析为never
。
示例
假设我们已经编写了这样的代码
type PrimitiveDataType = string | number | bigint | boolean | symbol | undefined | null;
type ConditionalType<T> = T extends PrimitiveDataType
? (v: T) => void
: never;
abstract class AbstractClass<T> {
abstract value: T;
protected conditionalFunctions: Map<ConditionalType<T>, number | undefined> = new Map();
}
class SomeClass<T extends PrimitiveDataType> extends AbstractClass<T> {
value: T;
constructor(value: T) {
super();
this.value = value;
}
someMethod() {
for (const someFn of this.conditionalFunctions.keys()) {
someFn(this.value);
}
}
}
TSPlayground链接
在上面的代码中,我创建了一个PrimitiveDataType
,它是JavaScript中所有原始数据类型的并集。
然后,我创建了一个ConditionalType<T>
,只有当T
是PrimitiveDataType
之一时,它才会解析为某个回调。
然后我创建了一个抽象泛型类,它有一个类型(ConditionalType<T>
)依赖于该类泛型值的字段。
最后,我创建了一个SomeClass
,它扩展了AbstractClass<T>
,并添加了泛型参数T
必须扩展PrimitiveDataType
的约束,我得到了这个错误:
TS2345: Argument of type 'PrimitiveDataType' is not assignable to parameter of type 'never'.
Type 'undefined' is not assignable to type 'never'.
结论
我想如果在SomeClass
T
中有一个约束,它必须是PrimitiveDataType
上的一个,那么TypeScript将解析conditionalFunctions
字段为Map<(v: T) => void, number | undefined>
类型。令我惊讶的是,TypeScript将此类型解析为Map<(v: never) => void, number | undefined>
,这对我来说是不清楚的,我不知道哪里是错误的方式,我该怎么想?
你能给我解释一下为什么会这样吗?或者可能是TypeScript编译器的bug?
观察
如果我只留下一个,键入PrimitiveDataType
,那么一切正常,但对于多个,我得到一个错误
编辑1
如果我的条件类型有两个以上可能的返回类型,那么分布式[T]
就不起作用,并给出这样的错误:
Expected 2 arguments, but got 1.
看起来TS现在把这个类型解析成了Array的Map类型,我现在完全不明白
- 示例**
type PrimitiveDataType = string | number | bigint | boolean | symbol | undefined | null;
type ConditionalType<T> = [T] extends [PrimitiveDataType]
? (v: T) => void
: T extends Array<unknown>
? (v: T, t: number) => void
: never;
abstract class AbstractClass<T> {
abstract value: T;
protected conditionalFunctions: Map<ConditionalType<T>, number | undefined> = new Map();
}
class SomeClass<T extends PrimitiveDataType> extends AbstractClass<T> {
value: T;
constructor(value: T) {
super();
this.value = value;
}
someMethod() {
for (const someFn of this.conditionalFunctions.keys()) {
someFn(this.value);
}
}
}
TSPlayground-编辑1
在我看来,这应该和这个例子一样:
type PrimitiveDataType = string | number | bigint | boolean | symbol | undefined | null;
type ConditionalType<T> = [T] extends [PrimitiveDataType]
? (v: T) => void
: T extends Array<unknown>
? (v: T, t: number) => void
: never;
const x: PrimitiveDataType = 12;
const y: ConditionalType<typeof x> = (param: number) => undefined;
TSPlayground-变量示例
没有问题,ConditionalType<typeof x>
被解决为(v: number) => void
,所以我所期望的。
3条答案
按热度按时间30byixjq1#
泛型类型
ConditionalType<T>
在给定联合时生成一个分布式类型。例如,给定并集
string | number
时,将生成以下类型请将条件的任意一侧括在方括号中以避免出现这种情况
上面的代码现在生成了下面的非分布式类型,它应该可以为抽象类工作
Playground
2admgd592#
我在 typescript 方面相当初级,我已经用了差不多一年半了,但我仍然会尽力帮助你,给予你一个可能的解释,解释你遇到的错误(至少我认为是解释),以及修复它的可能方法。
/* ===============解释============ /
一个原因,也是对我来说最有意义的原因,可能是类型收缩不够充分。为什么?
在您提供的代码中,您将类型
ConditionalType<T>
定义为一个函数,当T
是PrimitiveDataType
时,该函数返回void
;当T
不是PrimitiveDataType
时,该函数返回never
。然而,您使用此函数类型的方式可能不是最好的。此函数类型的用途是T
类型的narrow the type
,应该作为type guard
使用。但是,当您声明类SomeClass
时,T
类型扩展原始PrimitiveDataType
而不是narrowed
类型,因此在for
循环中,您将检查原始类型PrimitiveDataType
是否是narrowed
类型版本的有效key
(正如您在AbstractClass
中声明的那样),但是never
并不存在于PrimitiveDataType
上,这就是为什么你应该在类SomeClass
声明中扩展T
,如我在下面提供的第二个修复所示。这样的话,T
也会默认包含never
,所以value
类型会是narrowed
版本类型,也就是ConditionalType<PrimitiveDataType>
,而不是PrimitiveDataType
。我希望你明白我的意思(我的英语仍然不是很好哈哈)。
/ ===============修复============= */
我将提供两种方法来修复这个问题,第二种是类型安全性最高的方法。
我想你可以用两种方法解决这个问题:
第一种方法是将
PrimitiveDataType
定义为始终是函数类型:当然,在上面的例子中,可能
ConditionalType
不再是正确的名称,请找到一个更好的名称。链接到上面描述的例子的typescriptPlayground-〉TypescriptPlayground/* =============或================ */
或者你可以使用另一种方法,其中类型参数
T
被约束为ConditionalType<PrimitiveDataType>
类型的子类型。在这种情况下,T
扩展了ConditionalType<PrimitiveDataType>
,这意味着T
必须是function type
,它接受PrimitiveDataType
类型的参数并返回void
。链接到第二种方法的 typescript Playground-〉 typescript Playground
/* ==============编辑1 =============== /
EDIT 1怎么样
当你尝试使用多个参数时遇到的错误的解释几乎是一样的,它总是与类型收缩有关,但是修复非常非常简单,你只是忘了把第二个参数设为可选的。
/ ==============固定编辑1 ============= */
这里有完整的代码修复编辑1 -〉 typescript 操场 typescript 操场
请让我知道这是否有帮助
0aydgbwb3#
如上所述,分布式类型会产生问题,解决方法是将
extends
子句的范围缩小到所需的函数类型。首先,看一个工作的例子(基于你的原始代码)。我在下面添加了两个例子来展示一个工作的(字符串)和不工作的(对象)例子。顺便说一句,这是一个熟悉info指令的好机会,只需在一行中输入'// ^?',ts pladren就会给你一些提示信息。
因此,修复的关键是检查所需的函数(而不仅仅是类型):
读作:
雌兔
接受PrimitiveDataType参数并返回void的函数
延伸
一个接受我的类型的参数(泛型的T)并返回void的函数
?
如果是,则返回所需的函数类型,否则,返回never。