본문으로 건너뛰기

6장 배열

타입스크립트는 초기 배열에 어떤 데이터 타입이 있는지 기억하고 배열이 해당 데이터 타입에서만 작동하도록 제한한다. 이런 방식으로 배열의 데이터 타입을 하나로 유지한다.

const warriors = ['Artemisia', 'Boudica'];

warriors.push('Zenobia'); // OK

warriors.push(true); // ERROR

6.1 배열 타입

배열을 저장하기 위한 변수는 초깃값이 필요하지 않다.

let arrayOfNumbers: number[];

arrayOfNumbers = [4, 8, 15, 16, 23, 42];

배열 타입은 Array<number> 구문으로도 작성할 수 있다. 하지만 대부분의 개발자는 더 간단한 number[]를 선호한다.

6.1.1 배열과 함수 타입

배열 타입은 함수 타입에 무엇이 있는지를 구별하는 괄호가 필요한 구문 컨테이너이다. 괄호는 애너테이션의 어느 부분이 함수 반환 부분이고 어느 부분이 배열 타입 묶음인지를 나타내기 위해 사용한다.

// string 배열을 반환하는 함수
let createStrings: () => string[];

// 각각의 string을 반환하는 함수 배열
let stringCreators: (() => string)[];

6.1.2 유니언 타입 배열

배열의 각 요소가 여러 선택 타입 중 하나일 수 있음을 나타내려면 유니언 타입을 사용한다.

유니언 타입으로 배열 타입을 사용할 때 애너테이션의 어느 부분이 배열의 콘텐츠이고 어느 부분이 유니언 타입 묶음인지를 나타내기 위해 괄호를 사용해야 할 수도 있다.

// string 또는 number의 배열
let stringOrArrayOfNumbers: string | number[];

// 각각 number 또는 string인 요소의 배열
let arrayOfStringOrNumbers: (string | number)[];

타입스크립트는 배열의 선언에서 두 가지 이상의 요소 타입이 포함되는 경우 유니언 타입 배열임을 알게 된다. 배열의 요소 타입은 배열에 담긴 요소에 대한 모든 가능한 타입의 집합이다.

// (string | undefined)[]
const namesMaybe = ['Aqualtune', 'Blenda', undefined];

6.1.3 any 배열의 진화

초기에 빈 배열로 설정된 변수에서 타입 애너테이션을 포함하지 않으면 타입스크립트는 배열을 any[]로 취급하고 모든 콘텐츠를 받을 수 있다. 하지만 any 변수가 변경되는 것처럼 any[] 배열이 변경되는 것도 좋아하지 않는다. 타입 애너테이션이 없는 빈 배열은 잠재적으로 잘못된 값 추가를 허용해 타입스크립트의 검사기가 갖는 이점을 부분적으로 무력화한다.

// any[]
let values = [];

// string[]
values.push('');

// (number | string)[]
values[0] = 0;

6.1.4 다차원 배열

2차원 배열 또는 배열의 배열은 두 개의 [](대괄호)를 갖는다.

let arrayOfArraysOfNumbers: number[][];

arrayOfArraysOfNumbers = [
[1, 2, 3],
[2, 4, 6],
[3, 6, 9],
];

다차원 배열 타입은 배열 타입에 새로운 개념을 도입한 게 아니다. 2차원 배열은 원래의 타입을 가지며 끝에 []가 있고 그 뒤에 []를 추가한다고 생각하면 쉽다.

6.2 배열 멤버

타입스크립트는 배열의 멤버를 찾아서 해당 배열의 타입 요소를 되돌려주는 전형적인 인덱스 기반 접근 방식을 이해하는 언어이다.

const defenders = ['Clarenza', 'Dina'];

// string
const defender = defenders[0];

유니언 타입으로 된 배열의 멤버는 그 자체로 동일한 유니언 타입니다.

const soldiersOrDates = ['Deborah Sampson', new Date(1782, 6, 3)];

// string | Date
const soldierOrDate = soldiersOrDates[0];

6.2.1 주의 사항: 불안정한 멤버

배열은 타입 시스템에서 불안정한 소스이다. 기본적으로 타입스크립트는 모든 배열의 멤버에 대한 접근이 해당 배열의 멤버를 반환한다고 가정하지만 자바스크립트에서조차도 배열의 길이보다 큰 인덱스로 배열 요소에 접근하면 undefined를 제공한다.

function withElements(elements: string[]) {
console.log(elements[9001].length); // 오류 없음
}

withElements(["It's", 'over']);

타입스크립트는 검색된 배열의 멤버가 존재하는지 의도적으로 확인하지 않는다.

타입스크립트에는 배열 조회를 더 제한하고 타입을 안전하게 만드는 noUncheckedIndexedAccess 플래그가 있지만 이 플래그는 매우 엄격해서 대부분의 프로젝트에서 사용하지 않는다.

노트

noUncheckedIndexedAccess 옵션을 default로 사용하지 않는 이유

6.3 스프레드와 나머지 매개변수

6.3.1 스프레드

... 스프레드 연산자를 사용해 배열을 결합한다. 타입스크립트는 입력된 배열 중 하나의 값이 결과 배열에 포함될 것임을 이해한다.

입력된 배열이 동일한 타입이면 출력 배열도 동일한 타입이다. 서로 다른 타입의 두 배열을 함께 스프레드해 새 배열을 생성하면 새 배열은 두 개의 원래 타입 중 어느 하나의 요소인 유니언 타입 배열로 이해된다.

// string[]
const soldiers = ['Harriet Tubman', 'Joan of Arc', 'Khutulun'];

// number[]
const soldierAges = [90, 19, 45];

// (string | number)[]
const conjoined = [...soldiers, ...soldierAges];

6.3.2 나머지 매개변수 스프레드

타입스크립트는 나머지 매개변수로 배열을 스프레드하는 자바스크립트 실행을 인식하고 이에 대해 타입 검사를 수행한다. 나머지 매개변수를 위한 인수로 사용되는 배열은 나머지 매개변수와 동일한 배열 타입을 가져야 한다.

function logWarriors(greeting: string, ...names: string[]) {
for (const name of names) {
console.log(`${greeting}, ${name}!`);
}
}

const warriors = ['Cathay Williams', 'Lozen', 'Nzinga'];

logWarriors('Hello', ...warriors);

const birthYears = [1844, 1840, 1583];

logWarriors('Born in', ...birthYears); // ERROR

6.4 튜플

튜플 배열은 각 인덱스에 알려진 특정 타입을 가지며 배열의 모든 가능한 멤버를 갖는 유니언 타입보다 더 구체적이다. 튜플 타입을 선언하는 구문은 배열 리터럴처럼 보이지만 요소의 값 대신 타입을 적는다.

const pairLoose = [false, 123];

let yearAndWarrior: [number, string];

yearAndWarrior = [530, 'Tomyris']; // OK

yearAndWarrior = [false, 'Tomyris']; // ERROR

yearAndWarrior = [530]; // ERROR

자바스크립트에서는 단일 조건을 기반으로 두 개의 변수에 초깃값을 설정하는 것처럼 한 번에 여러 값을 할당하기 위해 튜플과 배열 구조 분해 할당을 함께 자주 사용한다.

let [year, warrior] = Math.random() > 0.5 ? [340, 'Archidamia'] : [1828, 'Rani of Jhansi'];

6.4.1 튜플 할당 가능성

타입스크립트에서 튜플 타입은 가변 길이의 배열 타입보다 더 구체적으로 처리된다. 가변 길이의 배열 타입은 튜플 타입에 할당할 수 없다.

// (boolean | number)[]
const pairLoose = [false, 123];

const pairTupleLoose: [boolean, number] = pairLoose; // ERROR

튜플 타입의 튜플에 얼마나 많은 멤버가 있는지 알고 있기 때문에 길이가 다른 튜플은 서로 할당할 수 없다.

const tupleThree: [boolean, number, string] = [false, 1583, 'Nzinga'];

const tupleTwoExact: [boolean, number] = [tupleThree[0], tupleThree[1]];

const tupleTwoExtra: [boolean, number] = tupleThree; // ERROR

나머지 매개변수로서의 튜플

튜플은 구체적인 길이와 요소 타입 정보를 가지는 배열로 간주되므로 함수에 전달할 인수를 저장하는 데 특히 유용하다. 타입스크립트는 ... 나머지 매개변수로 전달된 튜플에 정확한 타입 검사를 제공할 수 있다.

function logPair(name: string, value: number) {
console.log(`${name} has ${value}`);
}

const pairArray = ['Amage', 1];

logPair(...pairArray); // ERROR

const pairTupleIncorrect: [number, string] = [1, 'Amage'];

logPair(...pairTupleIncorrect); // ERROR

const pairTupleCorrect: [string, number] = ['Amage', 1];

logPair(...pairTupleCorrect); // OK

나머지 매개변수 튜플을 사용하고 싶다면 여러 번 함수를 호출하는 인수 목록을 배열에 저장해 함께 사용할 수 있다.

function logTrio(name: string, value: [number, boolean]) {
console.log(`${name} has ${value[0]} (${value[1]})`);
}

const trios: [string, [number, boolean]][] = [
['Amanitore', [1, true]],
['Amanitores', [2, false]],
['Ann E. Dunwoody', [3, false]],
];

trios.forEach(trio => logTrio(...trio)); // OK

trios.forEach(logTrio); // ERROR

6.4.2 튜플 추론

타입스크립트는 생성된 배열을 튜플이 아닌 가변 길이의 배열로 취급한다.

// 반환 타입: (string | number)[]
function firstCharAndSize(input: string) {
return [input[0], input.length];
}

// firstChar 타입: string | number
// size 타입: string | number
const [firstChar, size] = firstCharAndSize('Gudit');

명시적 튜플 타입

함수에 대한 반환 타입 애너테이션처럼 튜플 타입도 타입 애너테이션에 사용할 수 있다.

// 반환 타입: [string, number]
function firstCharAndSizeExplicit(input: string): [string, number] {
return [input[0], input.length];
}

// firstChar 타입: string | number
// size 타입: string | number
const [firstChar, size] = firstCharAndSize('Cathay Williams');

const 어서션

명시적 타입 애너테이션에 튜플 타입을 입력하는 작업은 명시적 타입 애너테이션을 입력할 때와 동일한 이유로 고통스러울 수 있다.

타입스크립트는 값 뒤에 넣을 수 있는 const 어서션인 as const 연산자를 제공한다. const 어서션은 타입스크립트에 타입을 유추할 때 읽기 전용이 가능한 값 형식을 사용하도록 지시한다.

// 타입: (string | number)[]
const unionArray = [1157, 'Tomoe'];

// 타입: readonly [1157, "Tomoe"]
const readonlyTuple = [1157, 'Tomoe'] as const;

const 어서션은 유연한 크기의 배열을 고정된 크기의 튜플로 전환하는 것을 넘어서 해당 튜플이 읽기 전용이고 값 수정이 예상되는 곳에서 사용할 수 없음을 나타낸다.

const pairMutable: [number, string] = [1157, 'Tomoe'];
pairMutable[0] = 1247; // OK

const pairAlsoMutable: [number, string] = [1157, 'Tomoe'] as const; // ERROR

const pairConst = [1157, 'Tomoe'] as const;
pairConst[0] = 1247; // ERROR

튜플은 반환하는 함수로부터 반환된 값은 보통 즉시 구조화되지 않으므로 읽기 전용인 튜플은 함수를 사용하는 데 방해가 되지 않는다.

// 반환 타입: readonly [string, number]
function firstCharAndSizeAsConst(input: string) {
return [input[0], input.length] as const;
}

// firstChar 타입: string
// size 타입: number
const [firstChar3, size3] = firstCharAndSizeAsConst('Ching Shih');