【问题标题】:How to use array.map with tuples in typescript?如何在打字稿中使用带有元组的array.map?
【发布时间】:2020-01-14 17:08:12
【问题描述】:

每当我在元组上使用array.map 时,Typescript 都会将其推断为通用数组。例如,这里有一些简单的 3x3 数独游戏:

const _ = ' ' // a "Blank"

type Blank = typeof _

type Cell = number | Blank

type Three = [Cell, Cell, Cell]

type Board = [Three, Three, Three]

const initialBoard: Board = [
    [_, 1, 3],
    [3, _, 1],
    [1, _, _],
]

// Adds a `2` to the first cell on the first row
function applyMove(board: Board): Board {
    // ????errors here
    const newBoard: Board =  board.map((row: Three, index: number) => {
        if (index === 0) return <Three> [2, 1, 3]
        return <Three> row
    })
    return newBoard
}

function applyMoveToRow(row: Three): Three {
    // return [2, 1, 3] // This works
    const newRow: Three = [
        2,
        ...row.slice(1, 3)
    ]
    return newRow
}

TS 错误是:

Type '[Cell, Cell, Cell][]' is missing the following properties from type 
 '[[Cell, Cell, Cell], [Cell, Cell, Cell], [Cell, Cell, Cell]]': 0, 1, 2 .  

here 在 TS Playground 中。

有没有办法告诉打字稿,当我映射一个元组时,它会返回一个相同类型的元组,而不仅仅是一个数组?我尝试过非常明确,注释我的所有返回值等,但它没有起到作用。

Typescript github 上有一个关于这个的讨论:https://github.com/Microsoft/TypeScript/issues/11312

但我无法从中找到解决方案。

【问题讨论】:

  • 您是否也在询问您的slice() 问题?
  • 你使用的是什么版本的 TypeScript?看起来好像在 3.4 中已修复...
  • 当然,解决这个问题的最简单方法是在map 之后添加` as Board`。并且在] 之后为slice() 问题加上` as Three`...
  • @HereticMonkey 是的,as Board 确实可以解决问题,但感觉就像一个“补丁”。此外,playground 在 TS 3.5 中,仍然显示错误。
  • 是的,我刚刚注意到...可能值得在他们的 GitHub 上提出问题。可能是回归。

标签: typescript tuples


【解决方案1】:

TypeScript 在调用 map() 时不会尝试保留元组长度。此功能在microsoft/TypeScript#11312 中提出请求,在microsoft/TypeScript#11252 中实现,并在microsoft/TypeScript#16223 中恢复,因为它与现实世界的代码有关。详情请见microsoft/TypeScript#29841

但如果你愿意,你可以merge in your own declarationArray.prototype.map() 签名,以说明它保留了元组的长度。这是一种方法:

interface Array<T> {
  map<U>(
    callbackfn: (value: T, index: number, array: T[]) => U,
    thisArg?: any
  ): { [K in keyof this]: U };
}

这使用polymorphic this 类型以及array/tuple mapped types 来表示转换。

那么你的代码可以写成如下:

function applyMove(board: Board): Board {
  return board.map(
    (row: Three, index: number) => (index === 0 ? applyMoveToRow(row) : row)
  );
}

function applyMoveToRow(row: Three): Three {
  return [2, row[1], row[2]];
}

不会有错误。请注意,我没有费心尝试处理Array.prototype.slice()。尝试表示 slice() 对元组类型所做的工作需要付出很大的努力,特别是因为没有真正支持 tuple length manipulation... 这意味着您可能需要一堆重载签名或其他类型的技巧才能获得它完成了。如果您只打算将 slice() 用于短数组,您不妨像我在上面对 [2, row[1], row[2]] 所做的那样使用索引访问,编译器确实可以理解。

或者,如果您打算将它用于更长的数组但在代码中只使用少量次数,您可能只想使用type assertion 告诉编译器您知道自己在做什么。就此而言,如果你只做map() 的次数很少,你也可以在这里使用类型断言来代替上面对map() 签名的重新声明:

function applyMove(board: Board): Board {
  return board.map(
    (row: Three, index: number) => (index === 0 ? applyMoveToRow(row) : row)
  ) as Board; // assert here instead of redeclaring `map()` method signature
}

无论哪种方式都有效...类型断言的类型安全性较低但更直接,而声明合并更安全但更复杂。

希望有所帮助;祝你好运!

Link to code

【讨论】:

  • 我想知道您如何跟踪所有这些打字稿问题?
  • @captain-yossarian 结合了 GitHub 搜索和对阅读问题的一般兴趣。
  • 我正在尝试关注这些问题,但有时它让我不知所措。顺便说一句,你能看看这个问题stackoverflow.com/questions/67914546/…吗?
【解决方案2】:

如果您不介意调整分配 initialBoard 的方式,您可以将您的 Board 定义更改为:

interface Board  {
    [0]: Three,
    [1]: Three,
    [2]: Three,
    map(mapFunction: (row: Three, index: number, array: Board) => Three): Board;
}

这就是您必须更改将文字分配给 Board 的方式的方式:

const initialBoard: Board = <Board><any>[
    [_, 1, 3],
    [3, _, 1],
    [1, _, _],
]

【讨论】:

  • 这太好了,我没有意识到你可以在这样的界面中添加编号键。你能解释一下为什么initialBoard声明需要额外的&lt;Board&gt;&lt;any&gt;吗?
  • 因为 Board 不再被声明为数组,所以您不能只将数组分配给您的 Board 变量而不进行强制转换。如果你只使用&lt;Board&gt;,你会收到一个关于可能无效转换的错误,基本上是说“如果你真的知道你在做什么,那么首先将其转换为任何,然后再转换为 Board”。
猜你喜欢
  • 2021-11-12
  • 2021-06-17
  • 1970-01-01
  • 2021-11-22
  • 1970-01-01
  • 2023-01-21
  • 2022-10-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多