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

时间:2019-06-20 17:10:29

标签: arrays typescript generics flatten

我正在尝试创建一个可以平整打字稿中嵌套数组的函数。

到目前为止,我有这个:

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]返回any,因为它无法推断item[prop]返回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,
    },
];

并返回一个包含所有嵌套数组内容的数组。

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

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

基本上,我是从C#的linq中寻找SelectMany

2 个答案:

答案 0 :(得分:1)

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


如何?

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[]Record<K, V[]>是一个对象,其键K的属性为V[]类型) 。它返回一个V[]

现在,尽管您确实需要告诉它,作为reduce的第二个参数的初始空数组应该是V[](因此{ {1}}。

这应该可以按您想要的方式工作。希望能有所帮助;祝你好运!

Link to code

更新:当对象具有不同类型的数组时,上述内容似乎无法很好地推断出来。您可能会发现自己必须像[] as V[]那样显式地键入它,这很多余而且很烦人。

以下是更复杂的签名,但具有更好的推断和更好的IntelliSense建议:

flattenArrayByKey<"anotherArray", number>(objectsWithNestedProperties, "anotherArray")

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

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)适合于这样的缩小。

希望再有帮助。祝好运!

Link to code

答案 1 :(得分:0)

结果证明ESNext具有Array.flatMap,完全可以满足我的需求。

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

  

语法

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

callback函数,它使用三个参数生成新Array的元素:

     
    

currentValue            数组中正在处理的当前元素。

         

index(可选)            数组中正在处理的当前元素的索引。

         

array(可选)            调用了数组映射。

  
     

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

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

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

它正是我想要的:

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

NB :IE,Edge和Samsung Internet不支持此功能。