为什么以 `T extends undefined` 为条件的 Typescript 类型,用 `boolean` 实例化 T,将 T 解析为 `never`?
Why does a Typescript type conditional on `T extends undefined`, with T instantiated with `boolean`, resolve T to `never`?
下面的代码试图定义一个函数的类型,当它的泛型参数是 undefined
时不带参数调用,但对于任何其他参数类型有 1 个参数。 (很可能有更好的方法来完成这个,我很乐意在评论中看到链接,但问题是为什么 Typescript 的工作方式与我预期的不同。)
当 T extends undefined
为 false 时,T
在 else 分支中似乎变成了 never
,但仅在函数参数列表中...
type OptionalArgBroken<Arg> = Arg extends undefined ?
() => void :
(arg: Arg) => void;
const suppressArgBroken:OptionalArgBroken<undefined> = function() { };
suppressArgBroken(); // Fine
const haveArgBroken:OptionalArgBroken<boolean> = function(b:boolean) { };
haveArgBroken(true); // Type error
正如你在Playground上看到的,上面最后一行给出了类型错误
Argument of type 'true' is not assignable to parameter of type 'never'.(2345)
阅读 https://github.com/microsoft/TypeScript/issues/31751 后,我尝试在 []
中包装 Arg
和 undefined
,这似乎解决了问题:
type OptionalArgWorks<Arg> = [Arg] extends [undefined] ?
() => void :
(arg: Arg) => void;
const suppressArgWorks:OptionalArgWorks<undefined> = function() { };
suppressArgWorks(); // Fine
const haveArgWorks:OptionalArgWorks<boolean> = function(b:boolean) { };
haveArgWorks(true); // Fine
尽管该修复有效,但这不是同一个问题:
type MakesSenseT = undefined extends undefined ? 'yes' : 'no'
const MakesSense:MakesSenseT = 'yes';
type ExtendsUndefined<T> = T extends undefined ? 'yes' : 'no'
const MakesSenseToo : ExtendsUndefined<undefined> = 'yes';
const MakesSenseThree : ExtendsUndefined<boolean> = 'no';
为什么我的原始代码不起作用?
正如所写,
type OptionalArgBroken<Arg> = Arg extends undefined ?
() => void :
(arg: Arg) => void;
是 distributive conditional type 因为被检查的类型 Arg
是一个裸泛型类型参数。
"Distributive" 意味着如果传入的 Arg
是一个 union,那么将分别为 union 的每个成员评估类型,然后重新组合在一起(因此整个联盟 分布 操作)。换句话说,OptionalArgBroken<A | B | C>
将与 OptionalArgBroken<A> | OptionalArgBroken<B> | OptionalArgBroken<C>
相同。
这可能不是您的意图,当您将支票包装在 []
中时您对结果感到满意这一事实证明了这一点(这使得支票类型不再 "naked" "clothing"它)。
此外,TypeScript 编译器将 boolean
类型视为 shorthand 用于 true
和 false
的联合,即所谓的 boolean literal types :
type Bool = true | false;
// type Bool = boolean
如果您使用 IntelliSense 将鼠标悬停在 IDE 中的 Bool
上,您会看到上面的 Bool
显示为 boolean
。
如果您将 boolean
视为单一类型而不是其他两种类型的联合,这可能会令人惊讶。当您将 boolean
传递给分布式条件类型时,会出现一个地方:OptionalArgBroken<boolean>
是 OptionalArgBroken<true | false>
即 OptionalArgBroken<true> | OptionalArgBroken<false>
即
type OABBool = OptionalArgBroken<boolean>;
// type OABBool = ((arg: false) => void) | ((arg: true) => void)
你传入了你认为是单一类型的东西,却得到了一个函数类型的联合。哎呀。 (参见 microsoft/TypeScript#37279)
并且函数类型的联合只能通过其参数的 交集 来安全调用。阅读 TS3.3 release notes on support for calling a union of functions 了解有关原因的信息。
但这意味着 OptionalArgBroken<boolean>
类型的值只能用 true & false
类型的参数调用,它被缩减为 never
(参见 microsoft/TypeScript#31838),因为没有值既是 true
又是 false
。
而因此,当你尝试调用haveArgBroken
时,它期望传入的参数是never
:
类型
const haveArgBroken: OptionalArgBroken<boolean> = function (b: boolean) { };
// haveArgBroken(arg: never): void
而true
不是never
类型,所以失败:
haveArgBroken(true); // Type error
这就是您的原始代码不起作用的原因。
请注意,
也会发生同样的事情
type ExtendsUndefined<T> = T extends undefined ? 'yes' : 'no'
但它是良性的,因为 ExtendsUndefined<boolean>
变成了 ExtendsUndefined<true> | ExtendsUndefined<false>
,也就是 'no' | 'no'
缩减为 'no'
。它恰好是你想要的,但这只是因为无法区分来自 true
的 'no'
和来自 false
.
的 'no'
好的,希望对您有所帮助;祝你好运!
下面的代码试图定义一个函数的类型,当它的泛型参数是 undefined
时不带参数调用,但对于任何其他参数类型有 1 个参数。 (很可能有更好的方法来完成这个,我很乐意在评论中看到链接,但问题是为什么 Typescript 的工作方式与我预期的不同。)
当 T extends undefined
为 false 时,T
在 else 分支中似乎变成了 never
,但仅在函数参数列表中...
type OptionalArgBroken<Arg> = Arg extends undefined ?
() => void :
(arg: Arg) => void;
const suppressArgBroken:OptionalArgBroken<undefined> = function() { };
suppressArgBroken(); // Fine
const haveArgBroken:OptionalArgBroken<boolean> = function(b:boolean) { };
haveArgBroken(true); // Type error
正如你在Playground上看到的,上面最后一行给出了类型错误
Argument of type 'true' is not assignable to parameter of type 'never'.(2345)
阅读 https://github.com/microsoft/TypeScript/issues/31751 后,我尝试在 []
中包装 Arg
和 undefined
,这似乎解决了问题:
type OptionalArgWorks<Arg> = [Arg] extends [undefined] ?
() => void :
(arg: Arg) => void;
const suppressArgWorks:OptionalArgWorks<undefined> = function() { };
suppressArgWorks(); // Fine
const haveArgWorks:OptionalArgWorks<boolean> = function(b:boolean) { };
haveArgWorks(true); // Fine
尽管该修复有效,但这不是同一个问题:
type MakesSenseT = undefined extends undefined ? 'yes' : 'no'
const MakesSense:MakesSenseT = 'yes';
type ExtendsUndefined<T> = T extends undefined ? 'yes' : 'no'
const MakesSenseToo : ExtendsUndefined<undefined> = 'yes';
const MakesSenseThree : ExtendsUndefined<boolean> = 'no';
为什么我的原始代码不起作用?
正如所写,
type OptionalArgBroken<Arg> = Arg extends undefined ?
() => void :
(arg: Arg) => void;
是 distributive conditional type 因为被检查的类型 Arg
是一个裸泛型类型参数。
"Distributive" 意味着如果传入的 Arg
是一个 union,那么将分别为 union 的每个成员评估类型,然后重新组合在一起(因此整个联盟 分布 操作)。换句话说,OptionalArgBroken<A | B | C>
将与 OptionalArgBroken<A> | OptionalArgBroken<B> | OptionalArgBroken<C>
相同。
这可能不是您的意图,当您将支票包装在 []
中时您对结果感到满意这一事实证明了这一点(这使得支票类型不再 "naked" "clothing"它)。
此外,TypeScript 编译器将 boolean
类型视为 shorthand 用于 true
和 false
的联合,即所谓的 boolean literal types :
type Bool = true | false;
// type Bool = boolean
如果您使用 IntelliSense 将鼠标悬停在 IDE 中的 Bool
上,您会看到上面的 Bool
显示为 boolean
。
如果您将 boolean
视为单一类型而不是其他两种类型的联合,这可能会令人惊讶。当您将 boolean
传递给分布式条件类型时,会出现一个地方:OptionalArgBroken<boolean>
是 OptionalArgBroken<true | false>
即 OptionalArgBroken<true> | OptionalArgBroken<false>
即
type OABBool = OptionalArgBroken<boolean>;
// type OABBool = ((arg: false) => void) | ((arg: true) => void)
你传入了你认为是单一类型的东西,却得到了一个函数类型的联合。哎呀。 (参见 microsoft/TypeScript#37279)
并且函数类型的联合只能通过其参数的 交集 来安全调用。阅读 TS3.3 release notes on support for calling a union of functions 了解有关原因的信息。
但这意味着 OptionalArgBroken<boolean>
类型的值只能用 true & false
类型的参数调用,它被缩减为 never
(参见 microsoft/TypeScript#31838),因为没有值既是 true
又是 false
。
而因此,当你尝试调用haveArgBroken
时,它期望传入的参数是never
:
const haveArgBroken: OptionalArgBroken<boolean> = function (b: boolean) { };
// haveArgBroken(arg: never): void
而true
不是never
类型,所以失败:
haveArgBroken(true); // Type error
这就是您的原始代码不起作用的原因。
请注意,
也会发生同样的事情type ExtendsUndefined<T> = T extends undefined ? 'yes' : 'no'
但它是良性的,因为 ExtendsUndefined<boolean>
变成了 ExtendsUndefined<true> | ExtendsUndefined<false>
,也就是 'no' | 'no'
缩减为 'no'
。它恰好是你想要的,但这只是因为无法区分来自 true
的 'no'
和来自 false
.
'no'
好的,希望对您有所帮助;祝你好运!