简体   繁体   English

Typescript 中递归 array.map 的类型

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM