TypeScript에서 고정 길이 배열을 선언하는 방법


104

TypeScript 유형에 대한 지식이 부족함을 입증 할 위험이 있습니다. 다음과 같은 질문이 있습니다.

다음과 같이 배열에 대한 유형 선언을 할 때 ...

position: Array<number>;

... 임의의 길이로 배열을 만들 수 있습니다. 그러나 특정 길이, 즉 x, y, z 구성 요소에 대해 3을 가진 숫자를 포함하는 배열을 원한다면 고정 길이 배열에 대한 유형을 만들 수 있습니까?

position: Array<3>

어떤 도움이나 설명을 주시면 감사하겠습니다!

답변:


164

자바 스크립트 배열에는 배열의 길이를 허용하는 생성자가 있습니다.

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

그러나 이것은 초기 크기 일 뿐이며 변경에 대한 제한은 없습니다.

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

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

18
튜플 유형은 초기 크기 만 확인하므로 arr초기화 된 후에도 "숫자"를 무제한으로 푸시 할 수 있습니다 .
benjaminz

4
사실, 그 시점에서 "무엇이든 진행"하는 것은 여전히 ​​javascript입니다. 적어도 타이프 라이터의 transpiler 적어도 소스 코드에서이 시행됩니다
henryJack

6
예를 들어 50과 같은 큰 배열 크기를 원할 경우 50 번 [number[50]]쓸 필요가 없도록 반복되는 유형으로 배열 크기를 지정하는 방법이 [number, number, ... ]있습니까?
Victor Zamanian

2
이것에 관한 질문을 찾았습니다. stackoverflow.com/questions/52489261/...
빅터 Zamanian에게

1
@VictorZamanian 아시다시피 교차하는 아이디어 {length: TLength}는 typed를 초과하더라도 typescript 오류를 제공하지 않습니다 TLength. 크기가 적용되는 n-length 유형 구문을 아직 찾지 못했습니다.
Lucas Morgan

22

튜플 접근 방식 :

이 솔루션은 튜플을 기반으로 하는 엄격한 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

(*)이 솔루션이 작동하려면 noImplicitAnytypescript 구성 지시문 을 활성화해야합니다 (일반적으로 권장되는 방법).


Array (ish) 접근 방식 :

이 솔루션은 Array추가 두 번째 매개 변수 (배열 길이)를 허용하는 유형 의 증가로 작동 합니다. Tuple 기반 솔루션 만큼 엄격하고 안전하지 않습니다 .

구문 예 :

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

1
감사! 그러나 오류없이 배열의 크기를 변경할 수 있습니다.
Eduard

1
var myStringsArray: FixedLengthArray<string, 2> = [ "a", "b" ] // LENGTH ERROR여기서 2가 3이되어야하는 것 같나요?
Qwertiy

배열 길이 변경을 방지하는 더 엄격한 솔루션으로 구현을 업데이트했습니다
colxi

@colxi FixedLengthArray에서 다른 FixedLengthArray로 매핑 할 수있는 구현이 가능합니까? 내가 의미하는 바의 예 :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 나는 map출력에 일반적인 배열 서명을 제공합니다. 귀하의 경우 가장 가능성이 높은 number[]유형
colxi

5

실제로 현재 타이프 스크립트로이를 달성 할 수 있습니다.

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

1
요소 수가 변수 인 경우 어떻게 사용합니까? 숫자 유형이 N이고 숫자가 "num"이면 const arr : FixedArray <number, N> = Array.from (new Array (num), (x, i) => i); "유형 인스턴스화가 지나치게 깊고 무한 할 수 있습니다"를 제공합니다.
Micha Schwab

2
@MichaSchwab 불행히도 비교적 적은 숫자로만 작동하는 것 같습니다. 그렇지 않으면 "너무 많은 재귀"를 의미합니다. 귀하의 경우에도 동일하게 적용됩니다. 나는 그것을 철저히 테스트하지 않았다 :(.
Tomasz Gawel

답장 해 주셔서 감사합니다! 가변 길이에 대한 해결책을 찾으면 알려주십시오.
Micha Schwab
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.