使用类型推断展平嵌套数组
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
)和V
,array[number][K]
的数组属性的元素类型。然后您可以将array
键入为Record<K, V[]>[]
而不是T[]
(A Record<K, V[]>
是一个对象,其键K
的属性为V[]
类型)。它返回一个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
【讨论】:
第一个不接受我的数组,因为它想要一个记录,我不太明白我应该在那里放什么记录。第二个返回所有可能数组的联合类型。 您能否提供一个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 - 文档推断协议的函数不能嵌套