배열과 튜플 — 고정 길이 배열, Rest Element, as const
배열은 같은 타입의 가변 길이 컬렉션이고, 튜플은 ** 각 위치별 타입이 고정된** 배열입니다.
기본 타입 편에서 배열과 튜플을 간단히 다뤘는데, 이번에는 Rest Element, Variadic Tuple, as const 등 심화 내용을 정리합니다.
배열 타입 심화
읽기 전용 배열
// readonly 배열 — 수정 메서드 사용 불가
const numbers: readonly number[] = [1, 2, 3];
// numbers.push(4); // ❌ Error
// numbers[0] = 10; // ❌ Error
// numbers.length = 0; // ❌ Error
// ReadonlyArray<T> — 동일
const names: ReadonlyArray<string> = ['홍길동', '김철수'];
// readonly 배열은 일반 배열에 할당 불가
// const mutable: number[] = numbers; // ❌ Error
// 반대는 가능
const readOnly: readonly number[] = [1, 2, 3] as number[];
다차원 배열
// 2차원 배열
const matrix: number[][] = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
// 타입 별칭으로 가독성 높이기
type Matrix = number[][];
type Grid<T> = T[][];
const boolGrid: Grid<boolean> = [
[true, false],
[false, true],
];
튜플 타입 심화
이름이 있는 튜플 요소(Labeled Tuples)
TypeScript 4.0부터 튜플 요소에 이름을 붙일 수 있습니다.
// 이름 없는 튜플 — 의미를 파악하기 어려움
type OldRange = [number, number];
// 이름 있는 튜플 — 가독성 향상
type Range = [start: number, end: number];
const range: Range = [0, 100];
// IDE에서 range[0]에 마우스를 올리면 'start'라는 힌트가 보임
선택적 튜플 요소
type FlexiblePoint = [x: number, y: number, z?: number];
const point2D: FlexiblePoint = [10, 20]; // OK
const point3D: FlexiblePoint = [10, 20, 30]; // OK
Rest Element가 있는 튜플
// 처음 두 요소는 고정, 나머지는 가변
type LogEntry = [timestamp: Date, level: string, ...messages: string[]];
const entry: LogEntry = [
new Date(),
'ERROR',
'서버 연결 실패',
'재시도 중...',
'3번째 시도',
];
Rest Element는 튜플의 ** 처음, 중간, 끝** 어디에든 올 수 있습니다.
// 중간에 Rest Element
type Sandwich = [bread: string, ...fillings: string[], bread2: string];
const sub: Sandwich = ['호밀', '치즈', '상추', '토마토', '호밀'];
// 처음에 Rest Element
type Trailing = [...heads: string[], last: number];
const t: Trailing = ['a', 'b', 'c', 42];
Variadic Tuple Types
TypeScript 4.0에서 도입된 기능으로, 제네릭으로 튜플을 조작할 수 있습니다.
// 두 튜플을 합치는 타입
type Concat<A extends unknown[], B extends unknown[]> = [...A, ...B];
type Result = Concat<[1, 2], [3, 4]>; // [1, 2, 3, 4]
// 튜플 앞에 요소 추가
type Prepend<T, Arr extends unknown[]> = [T, ...Arr];
type WithId = Prepend<number, [string, boolean]>; // [number, string, boolean]
// 실전 활용: 타입 안전한 함수 합성
function concat<A extends unknown[], B extends unknown[]>(
a: [...A],
b: [...B]
): [...A, ...B] {
return [...a, ...b];
}
const result = concat([1, 'hello'] as const, [true, 42] as const);
// readonly [1, 'hello', true, 42]
as const와 튜플
as const는 배열을 ** 읽기 전용 튜플 **로 변환합니다.
// 일반 배열 — number[]로 추론
const numbers = [1, 2, 3];
// as const — readonly [1, 2, 3]으로 추론
const tuple = [1, 2, 3] as const;
// 객체 안의 배열도 마찬가지
const config = {
methods: ['GET', 'POST', 'PUT'], // string[]
} as const;
// config.methods: readonly ['GET', 'POST', 'PUT']
as const의 실전 활용
// 라우트 정의에서 활용
const routes = ['/', '/about', '/contact'] as const;
type Route = (typeof routes)[number]; // '/' | '/about' | '/contact'
// 함수 매개변수로 사용
function navigate(route: Route) {
console.log(`${route}로 이동`);
}
navigate('/about'); // OK
// navigate('/blog'); // ❌ Error
면접에서 "배열에서 유니온 타입을 추출하는 방법"을 물어보면 as const + (typeof arr)[number] 패턴을 설명하면 됩니다.
구조 분해 할당과 타입
// 튜플 구조 분해
const [first, second]: [string, number] = ['hello', 42];
// 배열 구조 분해 — 타입은 추론됨
const [head, ...rest] = [1, 2, 3, 4]; // head: number, rest: number[]
// 튜플의 구조 분해 — 정확한 타입
const tuple = [1, 'hello', true] as const;
const [a, b, c] = tuple; // a: 1, b: 'hello', c: true
배열 메서드와 타입 추론
const numbers = [1, 2, 3, 4, 5];
// map — 반환 타입이 자동 추론됨
const doubled = numbers.map((n) => n * 2); // number[]
const strings = numbers.map((n) => String(n)); // string[]
// filter — 타입 가드를 쓰면 타입이 좁혀짐
const mixed: (string | number)[] = [1, 'hello', 2, 'world'];
// 일반 filter — (string | number)[]
const filtered = mixed.filter((item) => typeof item === 'string');
// 타입 가드 filter — string[]
const onlyStrings = mixed.filter(
(item): item is string => typeof item === 'string'
);
정리
readonly배열/튜플은 수정 메서드를 차단한다- 튜플 요소에 이름을 붙이면 가독성이 좋아진다 (Labeled Tuples)
- Rest Element로 가변 길이 튜플을 정의할 수 있다
as const는 배열을 읽기 전용 리터럴 튜플로 변환한다(typeof arr)[number]로 배열에서 유니온 타입을 추출할 수 있다filter에 타입 가드를 쓰면 반환 타입이 좁혀진다
댓글 로딩 중...