같은 종류만 담는 상자
이사할 때 박스를 쌓아본 적 있나요? 좋은 이삿짐센터는 박스마다 라벨을 붙입니다. "주방용품", "책", "옷". 각 박스에는 같은 종류의 물건만 들어갑니다. 덕분에 어느 박스를 열어도 무엇이 나올지 미리 알 수 있습니다.
TypeScript의 배열이 딱 이런 역할을 합니다. "이 배열에는 문자열만 들어온다"고 선언하면, 다른 타입의 값이 들어오는 순간 에러가 납니다.
배열 타입 표기
배열 타입을 표기하는 방법은 두 가지입니다.
// 방법 1: 타입[] 형식const names: string[] = ["김민수", "이영희", "박철수"];const scores: number[] = [95, 87, 92];const flags: boolean[] = [true, false, true];// 방법 2: Array<타입> 형식const names2: Array<string> = ["김민수", "이영희", "박철수"];const scores2: Array<number> = [95, 87, 92];
두 방법은 완전히 동일합니다. 보통 string[]처럼 짧은 방법을 더 많이 씁니다. Array<string>은 나중에 제네릭을 배울 때 맥락이 맞아떨어집니다.
배열에서 타입이 지켜지는 방식
배열에 잘못된 타입의 값을 넣으면 에러가 납니다.
const names: string[] = ["김민수", "이영희"];names.push("박철수"); // OKnames.push(42); // 에러! Argument of type 'number' is not assignable to parameter of type 'string'.names[0] = "최지영"; // OKnames[1] = false; // 에러!
배열 요소를 꺼낼 때도 TypeScript는 타입을 알고 있습니다.
const scores: number[] = [95, 87, 92];const first = scores[0]; // first의 타입은 numberconst sum = scores.reduce((acc, cur) => acc + cur, 0); // sum의 타입은 number
읽기 전용 배열
배열을 선언하고 나서 수정하지 못하게 막고 싶을 때는 readonly를 붙입니다.
const days: readonly string[] = ["월", "화", "수", "목", "금"];days[0] = "일"; // 에러! Index signature in type 'readonly string[]' only permits reading.days.push("토"); // 에러! Property 'push' does not exist on type 'readonly string[]'.
readonly가 붙으면 요소를 바꾸거나 추가하거나 삭제할 수 없습니다. 설정값처럼 한 번 정해지면 절대 바뀌면 안 되는 목록에 사용합니다.
ReadonlyArray<string> 형태로도 쓸 수 있습니다.
const days: ReadonlyArray<string> = ["월", "화", "수", "목", "금"];
칸이 나뉜 정리함 — 튜플
배열은 같은 종류의 값을 개수 제한 없이 담습니다. 그런데 가끔은 "첫 번째 칸에는 이름, 두 번째 칸에는 나이, 세 번째 칸에는 재학여부"처럼 각 칸의 역할이 고정된 자료구조가 필요합니다.
이것이 튜플(Tuple)입니다. 서랍이 나뉜 정리함처럼, 각 칸의 타입과 순서가 고정되어 있습니다.
// 튜플 타입: [타입1, 타입2, 타입3]const student: [string, number, boolean] = ["김민수", 20, true];
첫 번째 요소는 반드시 string, 두 번째는 number, 세 번째는 boolean이어야 합니다.
순서를 바꾸면 에러입니다.
const student: [string, number, boolean] = [20, "김민수", true];// 에러! Type 'number' is not assignable to type 'string'.
개수도 고정됩니다.
const pair: [string, number] = ["김민수", 20, true];// 에러! Type '[string, number, boolean]' is not assignable to type '[string, number]'.
튜플 요소에 이름 붙이기
TypeScript 4.0부터 튜플의 각 요소에 이름을 붙일 수 있습니다. 코드를 읽기 훨씬 편해집니다.
// 이름 없는 튜플const student: [string, number, boolean] = ["김민수", 20, true];// 이름 있는 튜플const student2: [name: string, age: number, isEnrolled: boolean] = ["김민수", 20, true];
이름을 붙여도 실제 동작은 동일합니다. 하지만 코드를 읽는 사람이 각 칸의 의미를 바로 알 수 있습니다.
튜플의 실제 활용
튜플은 함수가 여러 값을 반환할 때 자주 사용됩니다.
function getMinMax(numbers: number[]): [number, number] { const min = Math.min(...numbers); const max = Math.max(...numbers); return [min, max];}const [min, max] = getMinMax([3, 1, 4, 1, 5, 9, 2, 6]);// min의 타입은 number, max의 타입은 number
React의 useState 훅이 이 패턴을 사용합니다. [값, 세터함수] 형태의 튜플을 반환하기 때문에 구조 분해 할당으로 받는 것입니다.
읽기 전용 튜플
배열처럼 튜플에도 readonly를 붙일 수 있습니다.
const point: readonly [number, number] = [10, 20];point[0] = 30; // 에러!
배열 vs 튜플 비교
| 구분 | 배열 | 튜플 |
|---|---|---|
| 요소 타입 | 모두 같음 | 각 위치마다 다를 수 있음 |
| 요소 개수 | 유동적 | 고정 |
| 사용 예 | 점수 목록, 이름 목록 | 좌표, 키-값 쌍, 함수 다중 반환값 |
| 표기 | string[] |
[string, number] |
배열은 같은 종류의 값을 여러 개 담을 때, 튜플은 종류와 순서가 고정된 묶음을 표현할 때 사용합니다.
다음 챕터에서는 "이 변수에 담을 수 있는 값을 딱 몇 가지로 제한하고 싶다"는 상황을 해결하는 리터럴 타입과 열거형을 알아보겠습니다.