본문 바로가기
C.W.K.
Stream
Lesson 03 of 05 · published

읽기 전용 배열과 튜플: 변경 권한을 제한해

~8 min · arrays-tuples, readonly, immutability

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"읽기 전용 배열은 ‘이 참조를 통해 원본을 바꾸지 않겠다’는 함수의 약속이야."

두 표기와 같은 의미

readonly T[]ReadonlyArray<T>는 같은 읽기 전용 배열 타입이야. push, pop, splice, sort처럼 원본을 바꾸는 메서드는 사용할 수 없고, length 읽기, 인덱스 접근, map, filter, slice 같은 읽기 연산은 그대로 쓸 수 있어.

이 제한은 컴파일 시점에만 있어. 실행 중 배열을 얼리는 것은 아니므로 다른 변경 가능한 별칭을 통해 값이 바뀔 수 있다. 실제 불변성과 참조가 가진 쓰기 권한을 구별해야 해.

함수 입력의 좋은 기본값

함수가 배열을 읽기만 한다면 매개변수를 readonly T[]로 받아. 함수 몸에서 우발적으로 정렬하거나 원소를 추가하는 일을 막고, 호출자에게도 원본을 건드리지 않는다고 알려 줘. 변경 가능한 배열은 읽기 전용 매개변수에 안전하게 넘길 수 있어.

반대 방향은 허용되지 않아. 읽기 전용 배열을 변경 가능한 변수에 넣으면 그 변수로 원본을 바꿀 수 있기 때문이야. 변경이 필요하면 스프레드 문법으로 복사해 새 배열을 만들고, 복사 비용과 소유권 전환을 코드에 드러내.

튜플에도 같은 규칙

readonly [number, string]은 길이와 위치 타입을 유지하면서 각 칸의 재대입을 막아. as const로 만든 배열 리터럴이 자동으로 읽기 전용 튜플이 되는 이유도 이 규칙이야.

읽기 전용 튜플을 받는 제네릭을 설계할 때는 제약도 readonly unknown[]처럼 써야 변경 가능한 튜플과 읽기 전용 튜플을 모두 받을 수 있어. 불필요하게 변경 가능한 타입으로 제약하면 안전한 입력을 거부할 수 있다.

얕은 제한이라는 사실

readonly User[]은 배열 칸을 바꾸지 못하게 하지만 각 User의 속성까지 얼리지는 않아. 원소도 불변이어야 하면 Readonly<User>를 함께 쓰거나 도메인 모델 자체를 불변으로 설계해야 해.

읽기만 하는 함수는 쓰기 권한을 요구하지 마. 최소 권한 원칙이 배열 타입에도 그대로 적용돼.

피파의 고백

원본을 정렬할 생각이 없던 함수에서 sort 한 줄이 들어가 호출자의 화면 순서를 바꾼 적이 있어. 매개변수에 readonly가 있었다면 컴파일러가 바로 막았을 일이야. 작은 권한 제한이 큰 추적 비용을 없애.

Code

readonly modifier — 허용되는 것, 아닌 것·typescript
// 배열의 readonly modifier.
const frozen: readonly number[] = [1, 2, 3];
frozen[0] = 99;       // ❌ Index signature in type 'readonly number[]' only permits reading
frozen.push(4);       // ❌ Property 'push' does not exist on type 'readonly number[]'

// 읽기 연산은 여전히 허용.
const doubled = frozen.map((n) => n * 2);       // ✅
const length = frozen.length;                    // ✅

// readonly tuple.
type Pair = readonly [number, number];
const p: Pair = [1, 2];
p[0] = 99;            // ❌ readonly

// readonly 가 one-way 관계.
const mutable: number[] = [1, 2, 3];
const alsoFrozen: readonly number[] = mutable;   // ✅ readonly 로 widening
// const backToMutable: number[] = alsoFrozen;    // ❌ 복사 없이 readonly 떨굴 수 없음
const copy: number[] = [...alsoFrozen];          // ✅ 명시적 복사
readonly parameter — 작은 단어, 큰 invariant·typescript
// 함수 parameter — 채택해야 할 default.

function summarize(items: readonly string[]): string {
  // items.sort()   // ❌ mutate 함 — 잡힘
  return items.join(', ');
}

// Sort 필요하면 먼저 복사.
function summarizeSorted(items: readonly string[]): string {
  return [...items].sort().join(', ');
}

// Caller 한테 신호: '너의 데이터 안 만질 거.'
const raw = ['c', 'a', 'b'];
summarize(raw);          // ✅ — raw 그대로
summarizeSorted(raw);    // ✅ — raw 그대로

External links

Exercise

User[]에서 이메일 목록을 만드는 함수의 매개변수를 읽기 전용 배열로 바꿔. 정렬 같은 변경 코드가 깨지면 복사본을 사용하고 호출자 계약이 어떻게 안전해졌는지 봐.
Hint
map, filter, reduce 같은 새 값을 만드는 연산은 가능하고, sortsplice, 인덱스 대입은 막혀야 해. 변경이 필요하면 먼저 복사본을 만들어.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.