TypeScript - 固定长度数组<长度>类型?

hen*_*ack 69 types typescript

冒着证明我对TypeScript类型缺乏了解的风险 - 我有以下问题.

当你为这样的数组做一个类型声明时......

position: Array<number>;
Run Code Online (Sandbox Code Playgroud)

...它会让你制作一个任意长度的数组.但是,如果你想要一个包含具有特定长度的数字的数组,即对于x,y,z组件为3,你可以为固定长度的数组创建一个类型,就像这样吗?

position: Array<3>
Run Code Online (Sandbox Code Playgroud)

任何帮助或澄清赞赏!

Nit*_*mer 121

javascript数组有一个接受数组长度的构造函数:

let arr = new Array<number>(3);
console.log(arr); // [undefined × 3]
Run Code Online (Sandbox Code Playgroud)

但是,这只是初始大小,改变它没有限制:

arr.push(5);
console.log(arr); // [undefined × 3, 5]
Run Code Online (Sandbox Code Playgroud)

Typescript具有元组类型,允许您定义具有特定长度和类型的数组:

let arr: [number, number, number];

arr = [1, 2, 3]; // ok
arr = [1, 2]; // Type '[number, number]' is not assignable to type '[number, number, number]'
arr = [1, 2, "3"]; // Type '[number, number, string]' is not assignable to type '[number, number, number]'
Run Code Online (Sandbox Code Playgroud)

  • 元组类型仅检查初始大小,因此在初始化之后你仍然可以向你的`arr`推送无限量的"数字". (13认同)
  • 没错,在运行时它仍然是javascript,"那时候任何事情都会发生".至少打字稿转换器至少会在源代码中强制执行此操作 (4认同)
  • 如果我想要大的数组大小(例如50),可以使用一种重复类型指定数组大小的方法,例如“ [number [50]]”,这样就不必写“ [number,数字,...]`50次? (2认同)
  • 没关系,发现了一个关于这个的问题。/sf/ask/3674248301/ (2认同)

col*_*lxi 47

元组方法:

此解决方案提供了 基于元组的严格FixedLengthArray(又名 SealedArray)类型签名。

语法示例:

// Array containing 3 strings
let foo : FixedLengthArray<[string, string, string]> 
Run Code Online (Sandbox Code Playgroud)

这是最安全的方法,因为它可以防止访问超出边界的索引

执行 :

type ArrayLengthMutationKeys = 'splice' | 'push' | 'pop' | 'shift' | 'unshift' | number
type ArrayItems<T extends Array<any>> = T extends Array<infer TItems> ? TItems : never
type FixedLengthArray<T extends any[]> =
  Pick<T, Exclude<keyof T, ArrayLengthMutationKeys>>
  & { [Symbol.iterator]: () => IterableIterator< ArrayItems<T> > }
Run Code Online (Sandbox Code Playgroud)

测试:

var myFixedLengthArray: FixedLengthArray< [string, string, string]>

// Array declaration tests
myFixedLengthArray = [ 'a', 'b', 'c' ]  // ? OK
myFixedLengthArray = [ 'a', 'b', 123 ]  // ? TYPE ERROR
myFixedLengthArray = [ 'a' ]            // ? LENGTH ERROR
myFixedLengthArray = [ 'a', 'b' ]       // ? LENGTH ERROR

// Index assignment tests 
myFixedLengthArray[1] = 'foo'           // ? OK
myFixedLengthArray[1000] = 'foo'        // ? INVALID INDEX ERROR

// Methods that mutate array length
myFixedLengthArray.push('foo')          // ? MISSING METHOD ERROR
myFixedLengthArray.pop()                // ? MISSING METHOD ERROR

// Direct length manipulation
myFixedLengthArray.length = 123         // ? READ-ONLY ERROR

// Destructuring
var [ a ] = myFixedLengthArray          // ? OK
var [ a, b ] = myFixedLengthArray       // ? OK
var [ a, b, c ] = myFixedLengthArray    // ? OK
var [ a, b, c, d ] = myFixedLengthArray // ? INVALID INDEX ERROR
Run Code Online (Sandbox Code Playgroud)

(*) 此解决方案需要启用noImplicitAnytypescript配置指令才能工作(通常推荐的做法)


Array(ish) 方法:

此解决方案表现为Array类型的扩充,接受额外的第二个参数(数组长度)。不像基于元组的解决方案那么严格和安全。

语法示例:

let foo: FixedLengthArray<string, 3> 
Run Code Online (Sandbox Code Playgroud)

请记住,此方法不会阻止您访问超出声明边界的索引并为其设置值。

执行 :

type ArrayLengthMutationKeys = 'splice' | 'push' | 'pop' | 'shift' |  'unshift'
type FixedLengthArray<T, L extends number, TObj = [T, ...Array<T>]> =
  Pick<TObj, Exclude<keyof TObj, ArrayLengthMutationKeys>>
  & {
    readonly length: L 
    [ I : number ] : T
    [Symbol.iterator]: () => IterableIterator<T>   
  }
Run Code Online (Sandbox Code Playgroud)

测试:

var myFixedLengthArray: FixedLengthArray<string,3>

// Array declaration tests
myFixedLengthArray = [ 'a', 'b', 'c' ]  // ? OK
myFixedLengthArray = [ 'a', 'b', 123 ]  // ? TYPE ERROR
myFixedLengthArray = [ 'a' ]            // ? LENGTH ERROR
myFixedLengthArray = [ 'a', 'b' ]       // ? LENGTH ERROR

// Index assignment tests 
myFixedLengthArray[1] = 'foo'           // ? OK
myFixedLengthArray[1000] = 'foo'        // ? SHOULD FAIL

// Methods that mutate array length
myFixedLengthArray.push('foo')          // ? MISSING METHOD ERROR
myFixedLengthArray.pop()                // ? MISSING METHOD ERROR

// Direct length manipulation
myFixedLengthArray.length = 123         // ? READ-ONLY ERROR

// Destructuring
var [ a ] = myFixedLengthArray          // ? OK
var [ a, b ] = myFixedLengthArray       // ? OK
var [ a, b, c ] = myFixedLengthArray    // ? OK
var [ a, b, c, d ] = myFixedLengthArray // ? SHOULD FAIL
Run Code Online (Sandbox Code Playgroud)


Sus*_*uth 18

有点晚了,但如果您使用只读数组,这里是一种方法 ( [] as const) -

interface FixedLengthArray<L extends number, T> extends ArrayLike<T> {
  length: L
}

export const a: FixedLengthArray<2, string> = ['we', '432'] as const
Run Code Online (Sandbox Code Playgroud)

添加或删除const a值中的字符串会导致此错误 -

Type 'readonly ["we", "432", "fd"]' is not assignable to type 'FixedLengthArray<2, string>'.
  Types of property 'length' are incompatible.
    Type '3' is not assignable to type '2'.ts(2322)
Run Code Online (Sandbox Code Playgroud)

或者

Type 'readonly ["we"]' is not assignable to type 'FixedLengthArray<2, string>'.
  Types of property 'length' are incompatible.
    Type '1' is not assignable to type '2'.ts(2322)
Run Code Online (Sandbox Code Playgroud)

分别。

编辑(05/13/2022):相关的未来 TS 功能 -satisfies此处定义


Tom*_*wel 17

实际上,您可以使用当前的打字稿来实现这一点:

type Grow<T, A extends Array<T>> = ((x: T, ...xs: A) => void) extends ((...a: infer X) => void) ? X : never;
type GrowToSize<T, A extends Array<T>, N extends number> = { 0: A, 1: GrowToSize<T, Grow<T, A>, N> }[A['length'] extends N ? 0 : 1];

export type FixedArray<T, N extends number> = GrowToSize<T, [], N>;
Run Code Online (Sandbox Code Playgroud)

例子:

// OK
const fixedArr3: FixedArray<string, 3> = ['a', 'b', 'c'];

// Error:
// Type '[string, string, string]' is not assignable to type '[string, string]'.
//   Types of property 'length' are incompatible.
//     Type '3' is not assignable to type '2'.ts(2322)
const fixedArr2: FixedArray<string, 2> = ['a', 'b', 'c'];

// Error:
// Property '3' is missing in type '[string, string, string]' but required in type 
// '[string, string, string, string]'.ts(2741)
const fixedArr4: FixedArray<string, 4> = ['a', 'b', 'c'];
Run Code Online (Sandbox Code Playgroud)

编辑(经过很长时间)

这应该处理更大的尺寸(因为它基本上以指数方式增长数组,直到我们得到最接近的 2 次幂):

type Shift<A extends Array<any>> = ((...args: A) => void) extends ((...args: [A[0], ...infer R]) => void) ? R : never;

type GrowExpRev<A extends Array<any>, N extends number, P extends Array<Array<any>>> = A['length'] extends N ? A : {
  0: GrowExpRev<[...A, ...P[0]], N, P>,
  1: GrowExpRev<A, N, Shift<P>>
}[[...A, ...P[0]][N] extends undefined ? 0 : 1];

type GrowExp<A extends Array<any>, N extends number, P extends Array<Array<any>>> = A['length'] extends N ? A : {
  0: GrowExp<[...A, ...A], N, [A, ...P]>,
  1: GrowExpRev<A, N, P>
}[[...A, ...A][N] extends undefined ? 0 : 1];

export type FixedSizeArray<T, N extends number> = N extends 0 ? [] : N extends 1 ? [T] : GrowExp<[T, T], N, [[T]]>;
Run Code Online (Sandbox Code Playgroud)

  • 当元素数量是变量时如何使用它?如果我将 N 作为数字类型,将“num”作为数字,则 const arr: FixedArray&lt;number, N&gt; = Array.from(new Array(num), (x,i) =&gt; i); 给我“类型实例化太深并且可能是无限的”。 (2认同)
  • @MichaSchwab 不幸的是,它似乎只适用于相对较小的数字。否则它会告诉你“递归太多”。这同样适用于你的情况。我没有彻底测试它:(。 (2认同)

Tho*_* Vo 15

使用 typescript ,这是基于 Tomasz Gawel 的答案v4.6的超短版本

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

// usage
const x: Tuple<number,3> = [1,2,3];
x; // resolves as [number, number, number]
x[0]; // resolves as number
Run Code Online (Sandbox Code Playgroud)

还有其他方法可以强加 length 属性的值,但它不是很漂亮

// TLDR, don't do this
type Tuple<T, N> = { length: N } & readonly T[];
const x : Tuple<number,3> = [1,2,3]

x; // resolves as { length: 3 } | number[], which is kinda messy
x[0]; // resolves as number | undefined, which is incorrect
Run Code Online (Sandbox Code Playgroud)


Mar*_*ťko 6

对于任何需要比 @ThomasVo 提供的能够正确处理非文字数字的解决方案更通用的解决方案的人:

type LengthArray<
        T,
        N extends number,
        R extends T[] = []
    > = number extends N
        ? T[]
        : R['length'] extends N
        ? R
        : LengthArray<T, N, [T, ...R]>;
Run Code Online (Sandbox Code Playgroud)

我需要使用这种类型才能正确处理未知长度的数组。

type FixedLength = LengthArray<string, 3>; // [string, string, string]
type UnknownLength = LengthArray<string, number>; // string[] (instead of [])
Run Code Online (Sandbox Code Playgroud)