Cara mendeklarasikan Array dengan panjang tetap di TypeScript


105

Dengan risiko menunjukkan kurangnya pengetahuan saya seputar jenis TypeScript - Saya memiliki pertanyaan berikut.

Saat Anda membuat deklarasi tipe untuk array seperti ini ...

position: Array<number>;

... itu akan membiarkan Anda membuat array dengan panjang yang berubah-ubah. Namun, jika Anda menginginkan sebuah array yang berisi angka-angka dengan panjang tertentu yaitu 3 untuk komponen x, y, z, dapatkah Anda membuat tipe dengan untuk array dengan panjang tetap, seperti ini?

position: Array<3>

Setiap bantuan atau klarifikasi dihargai!

Jawaban:


165

Larik javascript memiliki konstruktor yang menerima panjang larik:

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

Namun, ini hanya ukuran awal, tidak ada batasan untuk mengubahnya:

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

Ketikan memiliki tipe tupel yang memungkinkan Anda menentukan array dengan panjang dan tipe tertentu:

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
Jenis tupel hanya memeriksa ukuran awal, sehingga Anda masih dapat memasukkan jumlah "angka" yang tidak terbatas ke Anda arrsetelah diinisialisasi.
benjaminz

4
Benar, pada saat itu masih javascript untuk "apa saja" pada saat itu. Setidaknya transpiler skrip akan memberlakukan ini dalam kode sumber setidaknya
henryJack

7
Jika saya ingin ukuran array besar seperti, katakanlah, 50, adakah cara untuk menentukan ukuran array dengan tipe berulang, seperti [number[50]], sehingga tidak perlu menulis [number, number, ... ]50 kali?
Victor Zamanian

2
Sudahlah, temukan pertanyaan tentang ini. stackoverflow.com/questions/52489261/…
Victor Zamanian

1
@VictorZamanian Agar Anda sadar, ide untuk berpotongan {length: TLength}tidak menyediakan kesalahan skrip ketikan jika Anda melebihi ketikan TLength. Saya belum menemukan sintaks tipe n-length yang dipaksakan oleh ukuran.
Lucas Morgan

23

Pendekatan Tuple:

Solusi ini menyediakan tanda tangan tipe FixedLengthArray (ak.a. SealedArray) yang ketat berdasarkan Tuple.

Contoh sintaks:

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

Ini adalah pendekatan teraman, mengingat ini mencegah mengakses indeks di luar batas .

Penerapan :

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

Tes:

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

(*) Solusi ini memerlukan pengaktifan konfigurasinoImplicitAny skrip skrip agar dapat berfungsi (praktik yang umumnya direkomendasikan)


Pendekatan Array (ish):

Solusi ini berperilaku sebagai augmentasi Arrayjenis, menerima parameter kedua tambahan (Panjang array). Tidak seketat dan seaman solusi berbasis Tuple .

Contoh sintaks:

let foo: FixedLengthArray<string, 3> 

Ingatlah bahwa pendekatan ini tidak akan mencegah Anda mengakses indeks dari batas yang dinyatakan dan menetapkan nilainya.

Penerapan :

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

Tes:

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
Terima kasih! Namun, masih dimungkinkan untuk mengubah ukuran larik tanpa mendapatkan kesalahan.
Eduard

1
var myStringsArray: FixedLengthArray<string, 2> = [ "a", "b" ] // LENGTH ERRORSepertinya 2 harus 3 di sini?
Qwertiy

Saya telah memperbarui implementasinya dengan solusi yang lebih ketat yang mencegah perubahan panjang array
colxi

@ Colxi Apakah mungkin untuk memiliki implementasi yang memungkinkan pemetaan dari FixedLengthArray ke FixedLengthArray lainnya? Contoh yang saya maksud: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 saya khawatir mapmemberikan tanda tangan array umum untuk outputnya. Dalam kasus Anda kemungkinan besar number[]tipe
colxi

6

Sebenarnya, Anda dapat mencapai ini dengan skrip jenis saat ini:

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

Contoh:

// 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
Bagaimana cara menggunakan ini jika jumlah elemen adalah variabel? Jika saya memiliki N sebagai jenis nomor dan "num" sebagai nomor, maka const arr: FixedArray <number, N> = Array.from (new Array (num), (x, i) => i); memberi saya "Instansiasi tipe terlalu dalam dan mungkin tak terbatas".
Micha Schwab

2
@MichaSchwab Sayangnya tampaknya ini hanya berfungsi dengan jumlah yang relatif kecil. Jika tidak, ia mengatakan "terlalu banyak rekursi". Hal yang sama berlaku untuk kasus Anda. Saya tidak mengujinya secara menyeluruh :(.
Tomasz Gawel

Terima kasih telah kembali kepada saya! Jika Anda menemukan solusi untuk panjang variabel, beri tahu saya.
Micha Schwab
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.