d8a80f6272
이름만 다른(표기 변형) [[위키링크]]를 대상 문서의 canonical 제목으로 치환해 끊겼던 1,200개 링크를 연결. 제목/파일명 정규화 일치만 적용하고 별칭 매칭은 과병합 위험으로 제외(애매성 가드). 원본은 _link_reconcile_backup/ 에 백업. 도구: Datacollect/scripts/link_reconcile_apply.mjs Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
171 lines
5.6 KiB
Markdown
171 lines
5.6 KiB
Markdown
---
|
|
id: wiki-2026-0508-deepreadonly
|
|
title: DeepReadonly
|
|
category: 10_Wiki/Topics
|
|
status: verified
|
|
canonical_id: self
|
|
aliases: [deep-readonly, recursive-readonly, immutable-type]
|
|
duplicate_of: none
|
|
source_trust_level: A
|
|
confidence_score: 0.9
|
|
verification_status: applied
|
|
tags: [typescript, type-utility, immutability]
|
|
raw_sources: []
|
|
last_reinforced: 2026-05-10
|
|
github_commit: pending
|
|
tech_stack:
|
|
language: typescript
|
|
framework: typescript-5.x
|
|
---
|
|
|
|
# DeepReadonly
|
|
|
|
## 매 한 줄
|
|
> **"매 `Readonly<T>` 의 surface-only — 매 nested mutation 가 still possible"**. 매 DeepReadonly<T> 가 매 recursively 의 every property 의 readonly 의 mark — 매 redux state, config object, frozen domain model 의 essential. TS 5.x 의 매 `const` type parameter + DeepReadonly 가 매 powerful combo.
|
|
|
|
## 매 핵심
|
|
|
|
### 매 vs Readonly
|
|
- `Readonly<T>` — 매 top-level only. 매 `obj.nested.mutate = X` 가 still allowed.
|
|
- `DeepReadonly<T>` — 매 every level 의 recursive freeze.
|
|
|
|
### 매 type-only vs runtime
|
|
- DeepReadonly 의 매 compile-time type guarantee — 매 runtime 의 mutation 의 X protect.
|
|
- 매 runtime freeze 의 `Object.freeze` (shallow) 또는 `deepFreeze` helper 사용.
|
|
- TypeScript 5.0+ `as const` 의 매 literal-level deep readonly 의 produce.
|
|
|
|
### 매 사용처
|
|
1. Redux/Zustand state shape — 매 mutation prevent.
|
|
2. Configuration schemas (env config, feature flags).
|
|
3. API response DTOs after parse.
|
|
4. Domain entities in DDD value objects.
|
|
5. Test fixtures (prevent accidental modification).
|
|
|
|
## 💻 패턴
|
|
|
|
### Basic DeepReadonly
|
|
```ts
|
|
type DeepReadonly<T> = T extends (...args: any[]) => any
|
|
? T
|
|
: T extends object
|
|
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
|
: T;
|
|
|
|
interface User {
|
|
id: string;
|
|
profile: { name: string; tags: string[] };
|
|
}
|
|
const u: DeepReadonly<User> = { id: '1', profile: { name: 'a', tags: ['x'] } };
|
|
// u.profile.name = 'b'; // ❌ Cannot assign
|
|
// u.profile.tags.push('y'); // ❌ readonly array
|
|
```
|
|
|
|
### Handles Map / Set / Array
|
|
```ts
|
|
type DeepReadonly<T> =
|
|
T extends (infer U)[] ? readonly DeepReadonly<U>[]
|
|
: T extends Map<infer K, infer V> ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
|
|
: T extends Set<infer U> ? ReadonlySet<DeepReadonly<U>>
|
|
: T extends Promise<infer U> ? Promise<DeepReadonly<U>>
|
|
: T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
|
: T;
|
|
```
|
|
|
|
### Brand-aware (preserve nominal types)
|
|
```ts
|
|
type Brand<T, B> = T & { readonly __brand: B };
|
|
type DeepReadonly<T> = T extends Brand<infer U, infer B>
|
|
? Brand<DeepReadonly<U>, B>
|
|
: T extends object
|
|
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
|
: T;
|
|
```
|
|
|
|
### Runtime deepFreeze pair
|
|
```ts
|
|
export function deepFreeze<T>(obj: T): DeepReadonly<T> {
|
|
if (obj && typeof obj === 'object' && !Object.isFrozen(obj)) {
|
|
Object.freeze(obj);
|
|
for (const key of Object.keys(obj)) deepFreeze((obj as any)[key]);
|
|
}
|
|
return obj as DeepReadonly<T>;
|
|
}
|
|
```
|
|
|
|
### `as const` + DeepReadonly
|
|
```ts
|
|
const config = {
|
|
features: { newCheckout: true, beta: ['user1', 'user2'] },
|
|
limits: { rpm: 100 },
|
|
} as const;
|
|
// 매 `as const` 가 매 every literal 의 deeply readonly + literal-typed 로 만듦.
|
|
type Config = typeof config;
|
|
// Config['features']['beta'] === readonly ['user1', 'user2']
|
|
```
|
|
|
|
### Mutable inverse (DeepWritable)
|
|
```ts
|
|
type DeepWritable<T> = T extends (...args: any[]) => any
|
|
? T
|
|
: T extends object
|
|
? { -readonly [K in keyof T]: DeepWritable<T[K]> }
|
|
: T;
|
|
|
|
function clone<T>(x: DeepReadonly<T>): DeepWritable<T> {
|
|
return structuredClone(x as T) as DeepWritable<T>;
|
|
}
|
|
```
|
|
|
|
### Zod + DeepReadonly
|
|
```ts
|
|
import { z } from 'zod';
|
|
const UserSchema = z.object({ id: z.string(), tags: z.array(z.string()) }).readonly();
|
|
type User = DeepReadonly<z.infer<typeof UserSchema>>;
|
|
```
|
|
|
|
### Function args (Redux reducer)
|
|
```ts
|
|
function reducer<S, A>(state: DeepReadonly<S>, action: A): S {
|
|
// state.x = ... // ❌ compile error
|
|
return { ...(state as S), updated: true } as S;
|
|
}
|
|
```
|
|
|
|
## 매 결정 기준
|
|
| 상황 | Approach |
|
|
|---|---|
|
|
| Literal config | `as const` |
|
|
| Generic state shape | `DeepReadonly<T>` utility |
|
|
| Runtime guarantee 필요 | `deepFreeze` + DeepReadonly |
|
|
| Performance hot path | 매 type-only DeepReadonly (no runtime cost) |
|
|
| Library author 가 expose | DeepReadonly + readonly arrays in public API |
|
|
|
|
**기본값**: type-only DeepReadonly + `as const` for literals; runtime `deepFreeze` only for security-sensitive boundaries.
|
|
|
|
## 🔗 Graph
|
|
- 부모: [[TypeScript-Type-System]] · [[Immutability]]
|
|
- 변형: [[Readonly]] · [[as const]]
|
|
- 응용: [[Zustand]] · [[Domain-Driven-Design]]
|
|
- Adjacent: [[Branded-Types]] · [[Zod]] · [[Structural-Sharing]]
|
|
|
|
## 🤖 LLM 활용
|
|
**언제**: 매 utility type 의 design / extension, 매 type error explanation, 매 readonly violation 의 codemod 작성.
|
|
**언제 X**: 매 runtime data validation (Zod 사용). 매 hot-path performance tuning (TS types 가 erased — runtime cost 0).
|
|
|
|
## ❌ 안티패턴
|
|
- **함수 type 의 readonly 적용**: 매 `(...args) => any` 가 readonly 의 의미 X — special-case 필요.
|
|
- **Date / RegExp 의 recurse**: 매 built-in instances 가 깨짐 — exclude 의 type guard.
|
|
- **DeepReadonly + cast away**: `state as Mutable` 가 매 type safety 의 destroy.
|
|
- **Runtime mutation through cast**: 매 `(state as any).x = 1` — 매 type lie 의 propagate.
|
|
- **Naive `keyof T` on union**: distributive conditional 의 사용.
|
|
|
|
## 🧪 검증 / 중복
|
|
- Verified (TypeScript 5.x docs, type-fest library, ts-toolbelt).
|
|
- 신뢰도 A.
|
|
|
|
## 🕓 Changelog
|
|
| 날짜 | 변경 |
|
|
|---|---|
|
|
| 2026-05-08 | Phase 1 |
|
|
| 2026-05-10 | Manual cleanup — DeepReadonly utility type variants and patterns |
|