推断所有值的类型签名

Jos*_*ulf 3 types typescript

这是在 TypeScript 4.0.2 中。类型系统可以从下面示例中的类型签名中推断出一些值。我很困惑为什么它不能推断const b.

谁能解释为什么,以及我如何编写可以的类型签名?

declare function identityA<T extends string[]>(p: readonly [...T]): T
declare function identityB<T extends any[]>(p: readonly [...T]): [...T]

const a = identityA(['r', 's'])  // ['r', 's']
const b = identityB([...a, 3])  // ['r', 's', number]
Run Code Online (Sandbox Code Playgroud)

jca*_*alz 6

TypeScript 的类型推断使用各种启发式方法来确定何时扩大文字类型以及何时使它们尽可能窄。例如,当你写

let s = ""; // string
let n = 0; // number
let b = true; // boolean
Run Code Online (Sandbox Code Playgroud)

该类型的变量 sn以及b加宽到stringnumberboolean,分别...假设是因为let声明让您重新分配值,你可能想这样做。另一方面,当你写

const sC = ""; // ""
const nC = 0; // 0
const bC = true; // true
Run Code Online (Sandbox Code Playgroud)

类型的变量sCnCbC作为保持在窄的""0以及true,分别。假设是因为您无法更改这些值,所以您没有理由在类型中允许多个单一值。


对于类型参数,例如T在您的identityA()identityB()函数中,编译器会执行其他操作。这在microsoft/TypeScript#10676 中提到

在调用表达式的类型参数推断期间,T如果 (...)T没有约束或其约束不包括原始类型或文字类型,则类型参数推断的类型将扩展为其扩展的文字类型

declare function identityA<T extends string[]>(p: readonly [...T]): T
Run Code Online (Sandbox Code Playgroud)

类型参数T有一个包含原始类型的约束string因此T不会从,比如说,扩大["r", "s"][string, string]。另一方面,在

declare function identityB<T extends any[]>(p: readonly [...T]): [...T]
Run Code Online (Sandbox Code Playgroud)

类型参数T的约束不包括任何文字或原始类型,因此T将从,比如说,扩大[3][number]。或者由于"r""s"类型已经从先前的推断T中未加宽,将从["r", "s", 3]到加宽["r", "s", number]

这很混乱,对吧?没关系,通过在参数类型 [...T]中使用可变元组类型,您p向编译器提示您要推断元组类型T而不仅仅是数组类型。


所以这就是它发生的原因。你能做些什么来修复它?

如果您可以控制调用站点,则可以使用const断言明确要求编译器为您传入的内容推断出最窄的可能类型。这发生在T推断之前:

const iBAsConst = identityB([...iA, 3] as const)  // ['r', 's', 3]
Run Code Online (Sandbox Code Playgroud)

但是您询问了如何更改签名以将identityB()的类型参数转换为文字优先推理的东西。您可以通过将 更改为any[]包含原语的内容来做到这一点。例如:

type Narrowable = string | number | boolean | symbol | 
  object | undefined | void | null | {};
declare function identityC<T extends Narrowable[]>(p: readonly [...T]): [...T]
const iC = identityC(["r", "s", 3]); // ['r', 's', 3] 
Run Code Online (Sandbox Code Playgroud)

几乎任何东西都可以分配给Narrowable,就像一个奇怪的any或者unknown也可以作为一个保持这个狭窄的提示。

万岁,对吧?


如果这一切对你来说都是邪恶的魔法,我同意。不久前,我打开了microsoft/TypeScript#30680来请求一些语法来明确要求as const函数签名端的类似“ ”的行为。然后你就可以写出类似的东西

// don't try this, it won't work:
declare function identityB<T extends any[] as const>(p: readonly [...T]): [...T];
Run Code Online (Sandbox Code Playgroud)

以获得相同的效果。唉,它只是作为今天(2020-09-24)的建议坐在那里,所以现在你必须继续练习黑暗艺术以获得你正在寻找的推理。

Playground 链接到代码