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

uujelgoq  于 2023-03-13  发布在  TypeScript
关注(0)|答案(8)|浏览(145)

我正在使用Typescript创建一个将棋游戏板。一个将棋板有9个等级和文件。
我想Assert一个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>)?

ozxc1zmp

ozxc1zmp1#

更新

使用Recursive conditional types(在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>>;

Playground

  • 原答复 *:

Typescript 3在元组类型中引入了rest元素
元组类型的最后一个元素可以是形式为...X的rest元素,其中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.

这里有一个警告,如果你试图访问元组范围之外的索引处的非元素,typescript不会警告你:

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'

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

mbskvtky

mbskvtky2#

一个快速简化的方法是创建一个Tuple9类型,该类型可用于创建矩阵的第一级和第二级:

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

jaql4c8m3#

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

bq3bfh9z

bq3bfh9z4#

您可以在元组类型别名的帮助下制作任意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>;
mpgws1up

mpgws1up5#

我是这样分三步完成的

  • 使用值类型的空 * 数组*元组初始化元组
  • 如果元组的长度等于所需的长度,则返回元组
  • 否则,递归地向元组添加值类型
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]
jaxagkaj

jaxagkaj6#

这可以在一条线上完成。

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

用法:

Tuple<MyType, 100>;
ztmd8pv5

ztmd8pv57#

对于常见用例(如本题),您尝试创建的类型不应具有会改变底层数组的不安全数组方法(如pushpop等),请注意以下几点:

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>'.
ruoxqz4g

ruoxqz4g8#

虽然递归方法很优雅并且有效,但它被限制在N〈1000。如果您进行以下实验,它将无法工作

type Tuple<T, N extends number, A extends T[] = []> =
    A extends { length: N } ? A : Tuple<T, N, [...A, T]>
// create an array of one thousand numbers
// can do it by e.g. entering Array(1000).fill(1) into dev tools and copy paste the result for simplicity
const thousandNumbers = [1, 1, /* ... more elements here ... */ 1] satisfies Tuple<number, 1000>

您将收到此错误:“类型示例化太深,可能是无限的。ts(2589)”
获取可以具有任意长度的通用固定大小数组的实用方法如下:

// ensure at least one element otherwise const x = { length: N } will satisfy the type definition!
type NonEmptyArray<T> = [T, ...T[]]

type Tuple<T, N extends number> = NonEmptyArray<T> & { length: N }

const thousandNumbers = [1, 1, /* ... more elements here ... */ 1] satisfies Tuple<number, 1000> // no error!

尽管如此,应该说这实际上等价于长度为1000的元组,但从技术上讲,它不是,而是始终显示为数组和{ length:N }

相关问题