TypeScript'te Sabit uzunlukta bir Dizi nasıl bildirilir


114

TypeScript türleriyle ilgili bilgi eksikliğimi gösterme riski altında - şu sorum var.

Böyle bir dizi için tür bildirimi yaptığınızda ...

position: Array<number>;

... keyfi uzunlukta bir dizi oluşturmanıza izin verir. Bununla birlikte, belirli uzunlukta sayılar içeren bir dizi istiyorsanız, yani x, y, z bileşenleri için 3, sabit uzunluklu bir dizi için ile bir tür oluşturabilir misiniz?

position: Array<3>

Herhangi bir yardım veya açıklama için minnettarız!

Yanıtlar:


181

JavaScript dizisi, dizinin uzunluğunu kabul eden bir kurucuya sahiptir:

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

Ancak, bu yalnızca başlangıç ​​boyutudur, bunu değiştirmeye yönelik herhangi bir kısıtlama yoktur:

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

Typescript, belirli bir uzunluk ve türlere sahip bir dizi tanımlamanıza izin veren tuple türlerine sahiptir:

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

22
Tuple türleri yalnızca başlangıç ​​boyutunu kontrol eder, böylece arrbaşlatıldıktan sonra yine de sınırsız miktarda "sayı" gönderebilirsiniz .
benjaminz

4
Doğru, bu noktada "her şey yolunda" hala javascript çalışma zamanında. En azından daktilo transpiler bunu kaynak kodunda en azından
zorlayacak

7
Örneğin 50 gibi büyük dizi boyutları istersem, 50 kez [number[50]]yazmak zorunda kalmamak için dizi boyutunu tekrar eden bir türle belirtmenin bir yolu var [number, number, ... ]mı?
Victor Zamanian

2
Boşver, bununla ilgili bir soru buldum. stackoverflow.com/questions/52489261/…
Victor Zamanian

1
@VictorZamanian Biliyorsunuz, kesişme fikri, yazılanı aşmanız durumunda {length: TLength}herhangi bir daktilo hatası sağlamaz TLength. Henüz boyutu zorunlu kılan n uzunlukta tür sözdizimi bulamadım.
Lucas Morgan

26

Tuple yaklaşımı:

Bu çözüm, Tuples tabanlı katı bir FixedLengthArray (ak.a. SealedArray) tipi imza sağlar.

Sözdizimi örneği:

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

Bu, endekslere sınırların dışına erişmeyi engellediği için en güvenli yaklaşımdır .

Uygulama:

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

Testler:

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

(*) Bu çözümün çalışması için noImplicitAnytypcript yapılandırma direktifinin etkinleştirilmesi gerekir (yaygın olarak önerilen uygulama)


Dizi (ish) yaklaşımı:

Bu çözüm Array, ek bir ikinci parametre (Dizi uzunluğu) kabul ederek , türün bir artırımı gibi davranır . Tuple tabanlı çözüm kadar katı ve güvenli değildir .

Sözdizimi örneği:

let foo: FixedLengthArray<string, 3> 

Bu yaklaşımın , belirtilen sınırlar dışındaki bir dizine erişmenizi ve üzerinde bir değer belirlemenizi engellemeyeceğini unutmayın .

Uygulama:

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

Testler:

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
Teşekkürler! Bununla birlikte, hata almadan dizinin boyutunu değiştirmek hala mümkündür.
Eduard

1
var myStringsArray: FixedLengthArray<string, 2> = [ "a", "b" ] // LENGTH ERROR2 burada 3 olmalı gibi görünüyor?
Qwertiy

Uygulamayı, dizi uzunluğu değişikliklerini önleyen daha katı bir çözümle güncelledim
colxi

@colxi FixedLengthArray öğesinden diğer FixedLengthArray öğelerine eşlemeye izin veren bir uygulamaya sahip olmak mümkün mü? Ne demek istediğime bir örnek: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 Korkarım mapçıktısına genel bir dizi imzası sağlıyor. Sizin durumunuzda büyük olasılıkla bir number[]tür
colxi

11

Aslında bunu mevcut yazı ile başarabilirsiniz:

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

Örnekler:

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

DÜZENLE (uzun bir süre sonra)

Bu, daha büyük boyutların üstesinden gelmelidir (temelde ikiye en yakın güce ulaşana kadar diziyi üssel olarak büyütür):

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

2
Eleman sayısı bir değişken olduğunda bunu nasıl kullanırım? Sayı türü olarak N ve sayı olarak "num" varsa, bu durumda const dizi: FixedArray <sayı, N> = Array.from (new Array (num), (x, i) => i); bana "Tip örneği aşırı derecede derin ve muhtemelen sonsuz" verir.
Micha Schwab

2
@MichaSchwab Maalesef yalnızca nispeten küçük sayılarla çalışıyor gibi görünüyor. Aksi takdirde "çok fazla özyineleme" olduğunu söyler. Aynısı davanız için de geçerlidir. Tam olarak test etmedim :(.
Tomasz Gawel

Bana geri döndüğün için teşekkürler! Değişken uzunluk için bir çözümle karşılaşırsanız, lütfen bana bildirin.
Micha Schwab

1
@MichaSchwab Güncellenen yanıta bakın.
Tomasz Gawel
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.