使用类型推断展平嵌套数组

Flatten nested array with type inference

我正在尝试创建一个可以在 typescript 中展平嵌套数组的函数。

到目前为止我有这个:

function flattenArrayByKey<T, TProp extends keyof T>(array: T[], prop: TProp): T[TProp] {
    return array.reduce((arr: T[TProp], item: T) => [...arr, ...(item[prop] || [])], []);
}

其中的 array.reduce 完全符合我的要求,但我无法让泛型与我的要求很好地配合。我认为我的问题是 item[prop] returns any 因为它无法推断 item[prop] returns T[TProp].

我的目标是一个可以采用这种结构的函数:

interface MyInterface {
    arrayProperty: string[];
    anotherArray: number[]
    someNumber: number;
}

const objectsWithNestedProperties: MyInterface[] = [
    {
        arrayProperty: ['hello', 'world'],
        anotherArray: [1, 2],
        someNumber: 1,
    },
    {
        arrayProperty: ['nice', 'to'],
        anotherArray: [3, 4],
        someNumber: 2,
    },
    {
        arrayProperty: ['meet', 'you'],
        anotherArray: [5, 6],
        someNumber: 3,
    },
];

和return一个包含所有嵌套数组内容的数组。

const result = flattenArrayByKey(objectsWithNestedProperties, 'arrayProperty');

result 应该看起来像 ['hello', 'world', 'nice', 'to', 'meet', 'you']

基本上我正在从 C# 的 linq 中寻找 SelectMany

注意:以下答案是在--strict模式下在TS3.5上测试的。如果您使用其他版本或编译器标志,您的里程可能会有所不同。


这个怎么样:

function flattenArrayByKey<K extends keyof any, V>(array: Record<K, V[]>[], prop: K): V[] {
    return array.reduce((arr, item) => [...arr, ...(item[prop] || [])], [] as V[]);
}

你必须告诉编译器 T[TProp] 将是一个数组。我没有尝试走那条路,而是让泛型成为 K(你称之为 TProp)和 V,数组的元素类型 属性 在 array[number][K]。然后你可以输入 array 作为 Record<K, V[]>[] 而不是 T[] (A Record<K, V[]> 是一个对象,其 属性 在键 K 的类型是 V[]).而且它returns一个V[]

现在编译器明白了你想做什么,尽管你确实需要告诉它作为 reduce 第二个参数的初始空数组应该是 V[](因此 [] as V[]).

这应该可以如您所愿。希望有所帮助;祝你好运!

Link to code

更新: 当对象具有不同类型的数组时,上面的推断似乎不太好。您可能会发现自己必须像 flattenArrayByKey<"anotherArray", number>(objectsWithNestedProperties, "anotherArray") 这样明确地键入它,这是多余且烦人的。

下面是一个更复杂的签名,但它有更好的推理和更好的 IntelliSense 建议:

type ArrayKeys<T> = { [K in keyof T]: T[K] extends any[] ? K : never }[keyof T];

function flattenArrayByKey<T extends Record<K, any[]>, K extends ArrayKeys<T>>(
  array: T[],
  prop: K
): T[K][number][] {
  return array.reduce(
    (arr, item) => [...arr, ...(item[prop] || [])],
    [] as T[K][number][]
  );
}

它在输入和输出方面的行为应该相同,但是如果您开始输入

const result = flattenArrayByKey(objectsWithNestedProperties, "");
// put cursor here and get intellisense prompts (ctrl-spc) ---^

它会建议"arrayProperty"(现在是"anotherArray")作为第二个参数,因为只有arrayProperty(和"anotherArray")适合这样减少。

希望再次有所帮助。祝你好运!

Link to code

原来 ESNext 有 Array.flatMap 完全符合我的要求。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap

Syntax

var new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {
   // return element for new_array
}[, thisArg])

callback Function that produces an element of the new Array, taking three arguments:

currentValue The current element being processed in the array.

index (Optional) The index of the current element being processed in the array.

array (Optional) The array map was called upon.

thisArg(可选) 执行回调时用作 this 的值。

要使用它,我需要将 esnext.array 添加到 tsconfig.json 中的 lib:

{
    "compilerOptions": {
         "lib": ["es2018", "dom", "esnext.array"]
     }
}

它完全符合我的要求:

objectsWithNestedProperties.flatMap(obj => obj.arrayProperty)
// returns ['hello', 'world', 'nice', 'to', 'meet', 'you']

注意: IE、Edge 和 Samsung Internet 不支持此选项。