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

时间:2019-09-12 19:15:13

标签: typescript tuples

每当我在元组上使用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游乐场。

有什么办法告诉打字稿,当我在一个元组上映射时,它将返回相同类型的元组,而不仅仅是数组?我尝试过非常明确,对所有返回值进行注释,等等,但这并没有解决问题。

在打字稿github上有一个关于此的讨论:https://github.com/Microsoft/TypeScript/issues/11312

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

2 个答案:

答案 0 :(得分:1)

如果需要,可以merge in your own declaration签名Array.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

答案 1 :(得分:0)

如果您不介意调整分配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, _, _],
]