如何在打字稿中使用带有元组的array.map?

Goo*_*dea 7 tuples typescript

每当我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
}
Run Code Online (Sandbox Code Playgroud)

TS 错误是:

Type '[Cell, Cell, Cell][]' is missing the following properties from type 
 '[[Cell, Cell, Cell], [Cell, Cell, Cell], [Cell, Cell, Cell]]': 0, 1, 2 .  
Run Code Online (Sandbox Code Playgroud)

在这里它是在一个TS游乐场。

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

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

但我一直无法从中得到解决方案。

jca*_*alz 8

TypeScript 不会在调用时尝试保留元组长度map()。此功能在microsoft/TypeScript#11312 中被请求,在microsoft/TypeScript#11252 中实现,并在microsoft/TypeScript#16223 中恢复,因为它在现实世界中引起了代码问题。有关详细信息,请参阅microsoft/TypeScript#29841

但是如果你愿意,你可以在你自己的声明中合并 ,的签名Array.prototype.map(),以说明它保留元组长度的事实。这是一种方法:

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

这使用多态this类型以及数组/元组映射类型来表示转换。

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

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]];
}
Run Code Online (Sandbox Code Playgroud)

并且不会有错误。请注意,我没有费心去处理Array.prototype.slice(). 尝试表示对slice()元组类型的作用将是大量的工作,特别是因为没有真正支持元组长度操作......这意味着您可能需要一堆重载签名或其他类型的技巧来完成它。如果你只打算使用slice()短数组,你不妨像我上面所做的那样使用索引访问[2, row[1], row[2]],编译器可以理解。

或者,如果您打算在代码中使用它来处理更长的数组但次数很少,您可能只想使用类型断言来告诉编译器您知道自己在做什么。就此而言,如果您只执行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
}
Run Code Online (Sandbox Code Playgroud)

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

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

代码链接

  • @captain-yossarian 是 GitHub 搜索和阅读问题的一般兴趣的结合。 (2认同)

Bra*_*don 1

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

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

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

const initialBoard: Board = <Board><any>[
    [_, 1, 3],
    [3, _, 1],
    [1, _, _],
]
Run Code Online (Sandbox Code Playgroud)