【问题标题】:Flatten nested array with type inference使用类型推断展平嵌套数组
【发布时间】:2019-11-03 13:30:24
【问题描述】:

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

到目前为止,我有这个:

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

【问题讨论】:

  • Array.flat() 已经在一些现代浏览器中工作
  • @Kokodoko 不允许我指定要展平的嵌套数组。

标签: arrays typescript generics flatten


【解决方案1】:

注意:以下答案是在--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)和Varray[number][K] 的数组属性的元素类型。然后您可以将array 键入为Record&lt;K, V[]&gt;[] 而不是T[](A Record&lt;K, V[]&gt; 是一个对象,其键K 的属性为V[] 类型)。它返回一个V[]

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

这应该可以按照您的意愿进行。希望有帮助;祝你好运!

Link to code

更新:当对象具有不同类型的数组时,上述内容似乎不能很好地推断出来。您可能会发现自己必须像 flattenArrayByKey&lt;"anotherArray", number&gt;(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

【讨论】:

  • 第一个不接受我的数组,因为它想要一个记录,我不太明白我应该在那里放什么记录。第二个返回所有可能数组的联合类型。
  • 您能否提供一个minimal reproducible example 说明什么不起作用?我提供了对您的示例有效的代码的链接,因此如果某些内容不起作用,我将无法重现它。
  • 我用一个额外的数组属性更新了 OP,它的返回类型是 (string | number) 的联合。
  • 嗯,它在打字稿操场上工作,但在 vscode 中我得到了联合类型。我们是TS 3.2.4,不知道跟版本有关系吗?
  • 是的,第二个可能不适用于 3.2,但第一个应该可以(您可以通过将版本更改为... 3.1.6 来查看 Playground)。 TypeScript 语言仍处于相当活跃的开发阶段,并且有很多编译器选项,所以除非有人另外提到,否则我想我一直在假设带有所有 --strict 标志的最新版本(现在是 3.5)。
【解决方案2】:

原来 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 产生新数组元素的函数,接受三个参数:

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

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

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

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

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

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

这正是我想要的:

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

注意:IE、Edge 和 Samsung Internet 不支持此功能。

【讨论】:

    猜你喜欢
    • 2011-05-11
    • 2017-12-26
    • 2013-03-19
    • 2022-12-07
    • 1970-01-01
    • 1970-01-01
    • 2018-07-26
    • 2014-05-28
    • 2021-12-31
    相关资源
    最近更新 更多