冒着证明我对TypeScript类型缺乏了解的风险 - 我有以下问题。
当你为这样的数组做一个类型声明时......
position: Array<number>;
...它会让你制作一个任意长度的数组。但是,如果你想要一个包含具有特定长度的数字的数组,即x,y,z组件为3,那么你可以为固定长度的数组创建一个类型吗?
position: Array<3>
任何帮助或澄清表示赞赏!
答案 0 :(得分:94)
javascript数组有一个构造函数,它接受数组的长度:
let arr = new Array<number>(3);
console.log(arr); // [undefined × 3]
然而,这只是初始大小,改变它没有限制:
arr.push(5);
console.log(arr); // [undefined × 3, 5]
Typescript具有tuple types,可让您定义具有特定长度和类型的数组:
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]'
答案 1 :(得分:26)
此解决方案基于元组提供了严格的 FixedLengthArray (也称为SealedArray)类型签名。
语法示例:
// Array containing 3 strings
let foo : FixedLengthArray<[string, string, string]>
这是最安全的方法,考虑到它防止对索引的访问。
实施方式:
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> > }
测试:
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
(*)此解决方案要求启用noImplicitAny
打字稿configuration directive才能正常工作(通常建议的做法)
此解决方案表现为Array
类型的扩充,接受附加的第二个参数(数组长度)。不像基于元组的解决方案那样严格和安全。
语法示例:
let foo: FixedLengthArray<string, 3>
请记住,这种方法不会阻止您访问已声明边界之外的索引并为其设置值。
实施方式:
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>
}
测试:
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
答案 2 :(得分:11)
实际上,您可以使用当前的打字稿来实现:
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>;
示例:
// 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'];
编辑(经过很长时间)
这应该处理更大的大小(因为基本上它会以指数形式增长数组,直到我们达到最接近的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]]>;
答案 3 :(得分:1)
聚会有点晚,但如果您使用只读数组 ([] as const
),这是一种方法 -
interface FixedLengthArray<L extends number, T> extends ArrayLike<T> {
length: L
}
export const a: FixedLengthArray<2, string> = ['we', '432'] as const
在 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)
或
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)
分别。