[英]Types for recursive array.map in Typescript
I have a simple case which transforms a nested string array to a nested number array, no flatting我有一个简单的案例,它将嵌套字符串数组转换为嵌套数字数组,没有扁平化
const strArr = ["1", "2", ["3", "4"], "5"];
function mapToNumber(item: string | Array<string>): number | Array<number> {
if (Array.isArray(item)) {
return item.map(mapToNumber); // line Hey
} else {
return Number(item);
}
}
console.log(strArr.map(mapToNumber));
However TS yells at me: Type '(number | number[])[]' is not assignable to type 'number | number[]'
但是 TS 对我大喊:
Type '(number | number[])[]' is not assignable to type 'number | number[]'
Type '(number | number[])[]' is not assignable to type 'number | number[]'
. Type '(number | number[])[]' is not assignable to type 'number | number[]'
。 Then I changed line Hey
to return item.map<number>(mapToNumber)
, doesn't work.然后我改变了行
Hey
return item.map<number>(mapToNumber)
,不起作用。 Function overloading came to my mind, I gave it a try:我想到了函数重载,我试了一下:
const isStringArray = (arr: any): arr is Array<string> => {
return Array.isArray(arr) && arr.every(item => typeof item === "string");
}
function mapToNumber(item: string): number;
function mapToNumber(item: Array<string>): Array<number>;
function mapToNumber(item: any) {
if (isStringArray(item)) {
return item.map<number>(mapToNumber);
} else {
return Number(item);
}
}
console.log(strArr.map(mapToNumber));
Even though I added the custom type guard, still doesn't work.即使我添加了自定义类型保护,仍然不起作用。
The logic is quite simple, but how can I define the correct type for this simple case?逻辑很简单,但是我如何为这个简单的案例定义正确的类型? The playground link
游乐场链接
Edit :编辑:
I gave generics a try, still doesn't work我试了一下泛型,还是不行
function mapToNumber3<T extends string | Array<string>>(item: T): T extends string ? number : Array<number> {
if (Array.isArray(item)) {
return item.map(mapToNumber3);
} else {
return Number(item);
}
}
In order to do that you can use f-bounded quantification :为此,您可以使用f 有界量化:
const strArr = ["1", "2", ["3", "4"], "5"];
const mapToNumber = (item: string) => parseInt(item, 10)
const isString = (item: unknown): item is string => typeof item === "string"
const isStringArray = (arr: any): arr is Array<string> => Array.isArray(arr) && arr.every(isString)
const map = <
N extends number,
Elem extends `${N}` | Array<Elem>,
T extends Array<T | Elem>,
>(arr: [...T]): Array<unknown> => {
return arr.map((item) => {
if (isStringArray(item)) {
return item.map(mapToNumber);
}
if (Array.isArray(item)) {
return map(item)
}
if (isString(item)) {
return mapToNumber(item)
}
return item
})
}
const result = map(["1", "2", ["3", "4", ['6', ['7']]], "5"])
T extends Array<T | Elem>
T extends Array<T | Elem>
- T is a recursive generic T extends Array<T | Elem>
- T 是递归泛型
The safest approach is to return Array<unknown>
since you don't know the deepnes of the array Playground最安全的方法是返回
Array<unknown>
因为你不知道数组Playground的深度
It is relatively easy to create recursive data structure type in typescript.在打字稿中创建递归数据结构类型相对容易。 See here , but it is hard to use it as a return type in function
请参阅此处,但很难将其用作函数中的返回类型
I would try to just define a type: type NestedArray<T> = T | NestedArray<T>[]
我会尝试只定义一个类型:
type NestedArray<T> = T | NestedArray<T>[]
type NestedArray<T> = T | NestedArray<T>[]
to represent a nested array of type T. Then, you just type your variables in your original functions, and it should work. type NestedArray<T> = T | NestedArray<T>[]
表示type NestedArray<T> = T | NestedArray<T>[]
类型的嵌套数组。然后,您只需在原始函数中键入变量,它就可以工作。
type NestedArray<T> = T | NestedArray<T>[]
const strArr: NestedAr
ray<string> = ["1", "2", ["3", "4"], "5"];
const isStringArray = (arr: any): arr is Array<string> => {
return Array.isArray(arr) && arr.every(item => typeof item === "string");
}
function mapToNumber(item: NestedArray<string>): NestedArray<number> {
if (isStringArray(item)) {
return item.map(mapToNumber);
} else {
return Number(item);
}
}
console.log(strArr.map(mapToNumber));
More information on this Github issue (basically, there's no good way to currently do this sadly).关于这个 Github 问题的更多信息(基本上,目前没有好的方法可以做到这一点)。 Proposal for what you want here .
在这里求你想要的。
I think you just have to use as
for now:我想你只需要使用
as
现在:
const strArr = ["1", "2", ["3", "4"], "5"];
function mapToNumber(item: string | string[]): number | number[] {
if (typeof item === 'string') {
return Number(item);
} else {
return item.map(mapToNumber) as number[]; // Here
}
}
console.log(strArr.map(item => mapToNumber(item)));
Your example with overloading technically works if you add one more overload.如果您再添加一个重载,那么您的重载示例在技术上是有效的。 Up to you if you think it's worth all the effort.
如果您认为所有的努力都值得,取决于您。
const strArr = ["1", "2", ["3", "4"], "5"];
const isStringArray = (arr: any): arr is Array<string> => {
return Array.isArray(arr) && arr.every(item => typeof item === "string");
}
function mapToNumber(item: string): number;
function mapToNumber(item: string[]): number[];
function mapToNumber(item: string | string[]): number | number[]; // Add this
function mapToNumber(item: any) {
if (isStringArray(item)) {
return item.map<number>(mapToNumber);
} else {
return Number(item);
}
}
console.log(strArr.map(mapToNumber));
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.