Cách khai báo Mảng có độ dài cố định trong TypeScript


104

Trước nguy cơ thể hiện sự thiếu hiểu biết của tôi về các loại TypeScript - tôi có câu hỏi sau.

Khi bạn khai báo kiểu cho một mảng như thế này ...

position: Array<number>;

... nó sẽ cho phép bạn tạo một mảng với độ dài tùy ý. Tuy nhiên, nếu bạn muốn một mảng chứa các số có độ dài cụ thể, tức là 3 cho các thành phần x, y, z, bạn có thể tạo kiểu với cho mảng có độ dài cố định không, như thế này?

position: Array<3>

Bất kỳ sự giúp đỡ hoặc làm rõ nào được đánh giá cao!

Câu trả lời:


164

Mảng javascript có một hàm tạo chấp nhận độ dài của mảng:

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

Tuy nhiên, đây chỉ là kích thước ban đầu, không có hạn chế khi thay đổi điều đó:

arr.push(5);
console.log(arr); // [undefined × 3, 5]

Typecript có nhiều kiểu cho phép bạn xác định một mảng với độ dài cụ thể và các kiểu:

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]'

18
Các loại tuple chỉ kiểm tra kích thước ban đầu, vì vậy bạn vẫn có thể đẩy số lượng "số" không giới hạn vào của mình arrsau khi nó được khởi tạo.
benjaminz

4
Đúng, nó vẫn là javascript trong thời gian chạy để "mọi thứ diễn ra" tại thời điểm đó. Ít nhất thì trình chuyển đổi kiểu chữ sẽ thực thi điều này trong mã nguồn ít nhất
henryJack

6
Trong trường hợp tôi muốn kích thước mảng lớn như, chẳng hạn, 50, có cách nào để chỉ định kích thước mảng với kiểu lặp lại, chẳng hạn như [number[50]], để không cần phải viết [number, number, ... ]50 lần không?
Victor Zamanian

2
Đừng bận tâm, đã tìm thấy một câu hỏi liên quan đến điều này. stackoverflow.com/questions/52489261/…
Victor Zamanian,

1
@VictorZamanian Xin lưu ý rằng ý tưởng giao nhau {length: TLength}không cung cấp bất kỳ lỗi sắp chữ nào nếu bạn vượt quá mức đã nhập TLength. Tôi vẫn chưa tìm thấy cú pháp loại n-length được thực thi theo kích thước.
Lucas Morgan,

22

Phương pháp tiếp cận Tuple:

Giải pháp này cung cấp chữ ký loại FixedLengthArray (ak.a. SealedArray) nghiêm ngặt dựa trên Tuples.

Ví dụ về cú pháp:

// Array containing 3 strings
let foo : FixedLengthArray<[string, string, string]> 

Đây là cách tiếp cận an toàn nhất, xem xét nó ngăn chặn việc truy cập các chỉ mục ra ngoài ranh giới .

Thực hiện :

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> > }

Kiểm tra:

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

(*) Giải pháp này đòi hỏi các noImplicitAnynguyên cảo bật chỉ thị cấu hình để hoạt động (phương pháp thường được khuyến nghị)


Cách tiếp cận Mảng (ish):

Giải pháp này hoạt động như một phần tăng thêm của Arraykiểu, chấp nhận một tham số thứ hai bổ sung (Độ dài mảng). Không nghiêm ngặt và an toàn như giải pháp dựa trên Tuple .

Ví dụ về cú pháp:

let foo: FixedLengthArray<string, 3> 

Hãy nhớ rằng cách tiếp cận này sẽ không ngăn bạn truy cập một chỉ mục ngoài ranh giới đã khai báo và đặt giá trị trên đó.

Thực hiện :

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>   
  }

Kiểm tra:

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

1
Cảm ơn! Tuy nhiên, vẫn có thể thay đổi kích thước của mảng mà không gặp lỗi.
Eduard

1
var myStringsArray: FixedLengthArray<string, 2> = [ "a", "b" ] // LENGTH ERRORCó vẻ như 2 nên là 3 ở đây?
Qwertiy

Tôi đã cập nhật việc triển khai bằng một giải pháp chặt chẽ hơn để ngăn thay đổi độ dài mảng
colxi

@colxi Có thể có một triển khai cho phép ánh xạ từ FixedLengthArray's sang FixedLengthArray khác không? Một ví dụ về ý tôi muốn nói:const threeNumbers: FixedLengthArray<[number, number, number]> = [1, 2, 3]; const doubledThreeNumbers: FixedLengthArray<[number, number, number]> = threeNumbers.map((a: number): number => a * 2);
Alex Malcolm

@AlexMalcolm, tôi e rằng mapcung cấp một chữ ký mảng chung cho đầu ra của nó. Trong trường hợp của bạn rất có thể là một number[]loại
colxi

5

Trên thực tế, bạn có thể đạt được điều này với bản đánh máy hiện tại:

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>;

Ví dụ:

// 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'];

1
Làm cách nào để sử dụng điều này khi số phần tử là một biến? Nếu tôi có N là kiểu số và "num" là số, thì const arr: FixedArray <number, N> = Array.from (new Array (num), (x, i) => i); cho tôi "Trình tạo kiểu quá sâu và có thể là vô hạn".
Micha Schwab

2
@MichaSchwab Thật không may, nó dường như chỉ hoạt động với số lượng tương đối nhỏ. Nếu không, nó thông báo "quá nhiều đệ quy". Điều tương tự cũng áp dụng cho trường hợp của bạn. Tôi đã không kiểm tra nó kỹ lưỡng :(.
Tomasz Gawel

Cảm ơn cho việc trở lại với tôi! Nếu bạn gặp một giải pháp cho độ dài thay đổi, vui lòng cho tôi biết.
Micha Schwab
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.