为什么以“T extends undefined”为条件的 Typescript 类型(用“boolean”实例化 T)会将 T 解析为“never”?

2023-11-26

以下代码尝试定义当泛型参数为时不带参数调用的函数的类型undefined,但对于任何其他参数类型有 1 个参数。 (很可能有更好的方法来实现这一点,我很乐意在评论中看到链接,但问题是为什么 Typescript 的工作方式与我预期的不同。)

When T extends undefined是假的,T似乎变成never在 else 分支中,但仅在函数参数列表内...

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

正如您在操场,上面的最后一行给出了类型错误

“true”类型的参数不可分配给“never”类型的参数。(2345)

看完之后https://github.com/microsoft/TypeScript/issues/31751,我尝试包裹Arg and undefined in []s,这似乎已经解决了问题:

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';

为什么我原来的代码不起作用?

上述代码的 Typescript Playground 链接


正如所写,

type OptionalArgBroken<Arg> = Arg extends undefined ?
    () => void :
    (arg: Arg) => void;

is a 分配条件类型因为正在检查的类型,Arg,是一个裸泛型类型参数。

“分布式”意味着如果Arg传入的是一个union,那么将分别评估联合体的每个成员的类型,然后重新组合在一起(因此操作是分散式整个联盟)。换句话说,OptionalArgBroken<A | B | C>将与以下相同OptionalArgBroken<A> | OptionalArgBroken<B> | OptionalArgBroken<C>.

这可能不是您的本意,您在签入时对结果感到满意就证明了这一点[](这使得检查类型不再通过“服装”来“裸体”)。


此外,TypeScript 编译器将boolean类型作为并集的简写true and false, 所谓的布尔文字类型:

type Bool = true | false;
// type Bool = boolean

如果您将鼠标悬停在Bool在带有 IntelliSense 的 IDE 中,您将看到Bool上面显示为boolean.

如果您想到这可能会感到惊讶boolean作为单一类型而不是其他两种类型的联合。当你经过时就会出现这种情况boolean分配条件类型:OptionalArgBroken<boolean> is OptionalArgBroken<true | false>这是OptionalArgBroken<true> | OptionalArgBroken<false>这是

type OABBool = OptionalArgBroken<boolean>;
// type OABBool = ((arg: false) => void) | ((arg: true) => void)

您传入了您认为是单一类型的内容,并得到了函数类型的联合。哎呀。 (看微软/TypeScript#37279)


并且函数类型的联合只能通过路口他们的参数。阅读关于支持调用函数联合的 TS3.3 发行说明有关原因的信息。

但这意味着类型的值OptionalArgBroken<boolean>只能使用类型参数来调用true & false,减少为never (see 微软/TypeScript#31838)因为不存在两者兼而有之的值true and false.

And 所以,当你尝试打电话时haveArgBroken,它期望传入的参数类型为never:

const haveArgBroken: OptionalArgBroken<boolean> = function (b: boolean) { };
// haveArgBroken(arg: never): void

And true不属于类型never,所以失败了:

haveArgBroken(true); // Type error

这就是为什么你的原始代码不起作用。


请注意,同样的事情也会发生

type ExtendsUndefined<T> = T extends undefined ? 'yes' : 'no'

但这是良性的,因为ExtendsUndefined<boolean>变成ExtendsUndefined<true> | ExtendsUndefined<false>这是'no' | 'no'减少到只是'no'。它恰好是你想要的,但只是因为没有办法区分'no'那来自true与来自的那个false.


Playground 代码链接

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么以“T extends undefined”为条件的 Typescript 类型(用“boolean”实例化 T)会将 T 解析为“never”? 的相关文章

随机推荐