Infer/narrow 来自同级 属性 的函数参数

Infer/narrow function argument from sibling property

我想根据同级 属性 缩小函数参数的类型。我知道存在类型会有所帮助,但它们不可用,所以我正在使用辅助函数。感谢评论中的 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,它需要什么作为输入以及它 returns.

最终目标是用打字稿编写辅助函数,但在 javascript 中用作类型注释。因此,在调用函数时我不能提供任何类型,也不能在标有注释的地方使用 as const

您希望 helper() 将数组文字 value ["one", "two", "three"] 视为具有 type ["one", "two", "three"];也就是说,像 ["one", "two", "three"] as const 这样的 tuple of string literal types instead of as just string[]. This is easy enough to do from the caller's side of the function; callers can use a const assertion。但是在调用者不想(或不能)使用 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. If a type parameter is constrained to a type that includes string, then the compiler will tend to infer string literal types. The same goes for number. So while an unconstrained T (like T extends unknown) will infer string when it sees "one", a constrained T extends string will infer "one". If you don't really want to constrain the type parameter, then you need to come up with the widest possible constraint that contains string and/or number explicitly. You can't use the unknown type,因为 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 variadic tuple types。如果 U 是类数组类型参数,那么编译器将倾向于看到 (arr: U) 并为 U 推断无序数组类型。但是如果你写 (arr: [...U]) 或 `(arr: readonly [...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;
    }
})

看起来不错。

Playground link to code