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

Posted

技术标签:

【中文标题】使用类型推断展平嵌套数组【英文标题】: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 不允许我指定要展平的嵌套数组。 【参考方案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 不支持此功能。

【讨论】:

以上是关于使用类型推断展平嵌套数组的主要内容,如果未能解决你的问题,请参考以下文章

为啥 TypeScript 在使用 concat 减少数组时会推断出“从不”类型?

Swift Opaque Type vs Protocols - 文档推断协议的函数不能嵌套

TypeScript 无法在扩展语法函数调用中推断数组类型

typescript 是不是能够理解数组是不是为非空并相应地推断某些数组方法的类型?

如何使 TypeScript 从数组中推断参数的数量和类型

TypeScript类型检查机制