从同级属性推断/缩小函数参数



我想根据同级属性缩小函数参数的类型。我知道存在类型会有所帮助,但它们不可用,所以我正在使用帮助程序函数。我目前在评论中感谢jcalz。

// helper function
function helper<T extends readonly unknown[]>(o: {
choices: T;
func: (opts: T[number]) => T[number];
}) {
return o;
}
helper({
choices: [
"one",
"two",
"three",
], // as const
func(o) { // should be "one" | "two" | "three", but is string
return o;
}
})

我希望func中的o"one" | "two" | "three"型,所以我的问题是打字稿不会将选择推断为元组,而是推断为string[]。我无法将as const添加到choices,因为该函数将在 javascript 中调用。

我知道我可以将选择和函数拆分为帮助函数的单独参数,但这是不可能的,因为对象中会有更多属性。这些属性的目的是描述func,它需要什么作为输入和返回什么。

最终目标是让帮助程序函数用打字稿编写,但在 javascript 中用作类型注释。因此,在调用函数或使用标有注释的函数as const时,我无法提供任何类型。

您希望helper()数组文本["one", "two", "three"]视为具有类型["one", "two", "three"]; 也就是说,作为字符串文本类型的元组,而不仅仅是string[]。 从函数的调用方端很容易做到这一点;调用方可以使用const断言,例如["one", "two", "three"] as const。 但是,如果调用方不想(或不能)使用const断言,最好让函数本身来执行此操作。

不久前我打开了Microsoft/TypeScript#30680,要求一些简单的方法来做到这一点。 也许它看起来像:

// INVALID TYPESCRIPT SYNTAX, DO NOT TRY THIS
//                        vvvvv
function helper<T extends const readonly unknown[]>(o: {
choices: T;
func: (opts: T[number]) => T[number];
}) {
return o;
}

其中const可以以某种方式应用于泛型类型参数。 不幸的是,这目前不是该语言的一部分。

幸运的是,有一些技巧可以让编译器的行为有些相似。


如果你想给编译器一个提示,它应该推断文字类型,你可以看看microsoft/TypeScript#10676。 如果类型参数被约束为包含string的类型,则编译器将倾向于推断字符串文本类型。number也是如此. 因此,虽然一个不受约束的T(如T extends unknown)在看到"one"时会推断出string,而一个受约束的T extends string会推断出"one"。 如果你真的不想约束类型参数,那么你需要提出尽可能广泛的约束,明确包含string和/或number。 您不能使用unknown类型,因为T extends string | unknown会立即折叠为T extends unknown。 但是,您可以执行以下操作

type Narrowable = string | number | bigint | boolean | 
symbol | object | undefined | void | null | {};

然后写T extends Narrowable而不是T extends unknown. 或者在您的情况下,T extends readonly Narrowable[]而不是T extends readonly unknown[].


如果您希望编译器推断元组而不是数组类型,还有其他技巧。 TypeScript 4.0 引入了可变元组类型。 如果U是一个类似数组的类型参数,那么编译器将倾向于看到(arr: U)并推断出U的无序数组类型。 但是如果你写(arr: [...U])或'(arr:只读[...U]),它将倾向于推断元组类型。


结合这些可以得到:

function helper<T extends readonly Narrowable[]>(o: {
choices: readonly [...T];
func: (opts: T[number]) => T[number];
}) {
return o;
}

您可以验证它是否有效:

helper({
choices: [
"one",
"two",
"three",
], // as const
func(o) { // "one" | "two" | "three"
return o;
}
})

看起来不错。

操场链接到代码

最新更新