【问题标题】:Infer type from array literal从数组文字推断类型
【发布时间】:2021-08-03 20:39:15
【问题描述】:

我有一个函数,它接受一个数组,并返回一个对象,其键是使用数组中的值设置的。

简化示例:

// arr will be an array of arrays of type [string] *or* [string, string]
function foo(arr) {
  const output = {}

  arr.forEach(value => {
    // if this value has length 2, use the second string as the key
    const key = value.length === 1 ? value[0] : value[1]

    // always use the first string as the value
    const value = value[0]

    output[key] = value
  })

  return output
}

例如,运行foo([['a', 'orange'], ['b'], ['c', 'apple']) 将产生{ orange: 'a', b: 'b', apple: 'c' } 类型为{ orange: string, b: string, apple: string } - 我们称该类型为R - 请注意,我并不真正关心R 的值是文字。这将是一个不错的奖励,但更通用的 string 类型就足够了。

使用 Typescript,我想从输入 arrT 的泛型推断 fooR 的返回类型。

我可以像这样输入T

function foo<T extends Array<[string, string?]>(arr: T) { ... }

那么是否可以从T 推断出R


更新

我可以看到的一个潜在问题是,这种运行违反了 Typescript 的能力限制,R 需要关心 T 的值的顺序,因为您当然可以指定重复的键。

即它不像类似的泛型对象类型那样纯粹-> 泛型对象类型映射语法[K in keyof T]: T[K] extends Thing ? ... : ...

如果这是一个问题,但有可能绕过它并简单地忽略它,即仅在没有任何重复键的情况下使这种类型安全,这对我的用例来说很好。

【问题讨论】:

  • 除非您声明数组const,否则您不能。一般来说,Typescript 编译器不能从数组的元素中推断出类型(因为只有在运行时才能知道确切的元素)。
  • 在调用代码中将数组声明为const 对我的用例来说很好!那么foo 的返回类型声明中的语法是什么?
  • 不,这不起作用,因为函数参数不是const,您可以使用 any 数组调用该函数。
  • @aleksxor 这太好了,谢谢!我已将支票交给@captain-yossarian,以便在没有const 的情况下设法做到这一点,但如果您将此添加为答案,我会投票赞成:-)
  • @captain-yossarian 是真的,你的回答很棒。查看 OP 代码让我将参数视为动态数组(内置代码),而不是文字数组:我很确定这不会起作用。

标签: typescript


【解决方案1】:

这是可行的:


type Values<T> = T[keyof T];

type Tuple<K, V = 1> = [K, V?]

type Elem = Tuple<any, any>

/**
 * Infers all elements in tuple and converts it
 * to object
 */
type Predicate<T> =
    T extends Tuple<infer K, infer V>
    ? V extends PropertyKey
    ? Record<V, K>
    : K extends PropertyKey
    ? Record<K, K>
    : never
    : never

/**
 * Iterate through argument
 */
type Reducer<
    Arr extends ReadonlyArray<Tuple<any, any>>,
    Result extends Record<string, any> = {}
    > = Arr extends []
    // last step of iteration
    ? Result
    // if there are still tuples in the array
    : Arr extends readonly [infer H, ...infer Tail]
    ? Tail extends ReadonlyArray<Tuple<any, any>>
    ? H extends Elem
    // call utility type recursively and produce record type with help of predicate
    ? Reducer<Tail, Result & Predicate<H>>
    : never
    : never
    : never

// we need to infer each key and property, thats why I used extra K,V generics
declare function foo<K extends string, V extends string, Tuples extends Tuple<K, V>[]>(tuples: [...Tuples]): Reducer<[...Tuples]>

foo([['a', 'orange'], ['b'], ['c', 'apple']]) // Record<"orange", "a"> & Record<"b", "b"> & Record<"apple", "c">

Playground

在这种情况下不需要使用const 断言

更多例子你可以找到in my blog

Here你可以找到如何从函数参数推断其他数据结构

【讨论】:

  • 这太不可思议了! const 和 TIL 关于 infer 的真正令人印象深刻的解决方法。谢谢!
【解决方案2】:

声明函数参数as const时工作的版本:

type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

type Result<T extends readonly [string] | readonly [unknown, string]> = UnionToIntersection<
    T extends readonly [infer V] ? { [k in Extract<V, string>]: V } :
    T extends readonly [infer V, infer K] ? { [k in Extract<K, string>]: V } : never
>

playground link

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-05
    • 1970-01-01
    • 2018-06-09
    • 2023-03-20
    • 2021-08-05
    • 1970-01-01
    • 2020-03-22
    相关资源
    最近更新 更多