Typescript:我可以定义一个 n 长度的元组类型吗?

问题描述 投票:0回答:9

我正在使用 Typescript 创建一个将棋游戏板。将棋盘有 9 个等级和档。

我想将 9x9 多维数组断言为一种类型,以确保数组的大小和内容。

目前我正在用这种方式创建 9x9 板类型:

type Board9x9<P> = [
  [P, P, P, P, P, P, P, P, P],
  [P, P, P, P, P, P, P, P, P],
  [P, P, P, P, P, P, P, P, P],
  [P, P, P, P, P, P, P, P, P],
  [P, P, P, P, P, P, P, P, P],
  [P, P, P, P, P, P, P, P, P],
  [P, P, P, P, P, P, P, P, P],
  [P, P, P, P, P, P, P, P, P],
  [P, P, P, P, P, P, P, P, P]
];

interface IShogiBoardInternalState {
    board: Board9x9<IShogiPiece>;
    playerName: string;
    isYourTurn: boolean;
}

问题:是否有一种不那么繁琐、更通用的方法来定义我称之为

Board9x9<P>
的元组类型?

typescript
9个回答
107
投票

更新

使用 递归条件类型(在 TypeScript 4.1.0 中添加)可以:

type Tuple<T, N extends number> = N extends N ? number extends N ? T[] : _TupleOf<T, N, []> : never;
type _TupleOf<T, N extends number, R extends unknown[]> = R['length'] extends N ? R : _TupleOf<T, N, [T, ...R]>;

type Tuple9<T> = Tuple<T, 9>;
type Board9x9<P> = Tuple9<Tuple9<P>>;

游乐场



原答案

Typescript 3 引入了元组类型中的剩余元素

元组类型的最后一个元素可以是 ...X 形式的剩余元素,其中 X 是数组类型

为了限制元组的长度,我们可以使用与

{ length: N }

的交集
type Tuple<TItem, TLength extends number> = [TItem, ...TItem[]] & { length: TLength };

type Tuple9<T> = Tuple<T, 9>;
type Board9x9<P> = Tuple9<Tuple9<P>>;

这在初始化

Tuple
类型的变量时起作用:

const t: Tuple<number, 1> = [1, 1] // error: 'length' incompatible.

这里需要注意,如果您尝试访问索引处超出元组范围的非元素,打字稿不会警告您:

declare const customTuple: Tuple<number, 1>;
customTuple[10] // no error here unfortunately

declare const builtinTuple: [number];
builtinTuple[10] // error: has no element at index '10'

有一个建议添加一种通用方法来指定元组类型的长度。


25
投票

一个快速简化方法是创建一个

Tuple9
类型,可用于创建矩阵的第一级和第二级:

type Tuple9<T> = [T, T, T, T, T, T, T, T, T]
type Board9x9<P> = Tuple9<Tuple9<P>>

6
投票

您可以借助元组类型别名制作任意 NxN 板:

type Tuple<T, N extends number, A extends any[] = []> = A extends { length: N } ? A : Tuple<T, N, [...A, T]>;

所以在你的情况下你会做类似的事情:

type Tuple<T, N extends number, A extends any[] = []> = A extends { length: N } ? A : Tuple<T, N, [...A, T]>;

type Board9x9<P> = Tuple<Tuple<P, 9>, 9>;

4
投票
type PushFront<TailT extends any[], FrontT> = (
  ((front : FrontT, ...rest : TailT) => any) extends ((...tuple : infer TupleT) => any) ?
  TupleT :
  never
)

type Tuple<ElementT, LengthT extends number, OutputT extends any[] = []> = {
  0 : OutputT,
  1 : Tuple<ElementT, LengthT, PushFront<OutputT, ElementT>>
}[
  OutputT["length"] extends LengthT ?
  0 :
  1
]

//type t3 = [string, string, string]
type t3 = Tuple<string, 3>
//type length = 0 | 3 | 1 | 2
type length = Partial<Tuple<any, 3>>['length']

添加通用方法来指定元组类型的长度#issuecomment-513116547


4
投票

我是这样分三步完成的

  • 使用值类型的空数组tuple初始化元组
  • 如果元组的长度等于所需长度,则返回元组
  • 否则,递归地将值类型添加到元组中
type Tuple<V, N extends number, T extends V[] = []> =
    N extends T['length'] ? T : Tuple<V, N, [...T, V]>;

用途:

type String3 = Tuple<string, 3>; // [string, string, string]
type String3Number = [...String3, number]; // [string, string, string, number]

4
投票

这可以一行完成。

type Tuple<T, N, R extends T[] = []> = R['length'] extends N ? R : Tuple<T, N, [...R, T]>;

用途:

Tuple<MyType, 100>;

0
投票

对常见用例(如本问题)的思考,其中您尝试创建的类型不应具有会改变底层数组的不安全数组方法(如

push
pop
等):

const board: Tuple<string, 4> = ["a", "b", "c", "d"];
board.pop()
const fourthElement: string = board[3]; // No TS error
fourthElement.toUpperCase() // Errors when run, but no TS error

不要使用元组,请考虑使用仅限某些索引的索引签名:

// type BoardIndicies = 0 | 3 | 1 | 2
type BoardIndicies = Partial<Tuple<never, 3>>['length']

const board: Record<BoardIndicies, string> = ["a", "b", "c", "d"];
board.pop() // ERROR: Property 'pop' does not exist on type 'Record<0 | 3 | 1 | 2, string>'.

0
投票

扩展已接受的解决方案https://stackoverflow.com/a/52490977/1712683

您可以通过从继承自 Array.prototype 的默认类型中删除这些类型来处理限制人们更新数组的情况

type Exclude = 'push' | 'pop' | 'splice'
type Tuple<T, L extends number> = Omit<T[], Exclude> & {length: L}

type Board9x9<P> = Tuple<Tuple<P, 9>, 9>

0
投票

基于 mstephen19Joey Kilpatrick's 的答案,您可以使用

readonly
前缀确保元组不会发生变化,而无需选择或省略属性:

type Tuple<T, N, R extends readonly T[] = []> = R['length'] extends N ? R : Tuple<T, N, readonly [...R, T]>;

readonly
关键字确保元组一旦创建就无法修改。您仍然可以使用无副作用的方法,例如
slice
。但是,改变数组的方法(例如
push
pop
splice
)在只读元组上不可用。

© www.soinside.com 2019 - 2024. All rights reserved.