웹 및 프런트엔드 내부: 브라우저 엔진, JavaScript 런타임 및 React 조정¶
내부 내용: 브라우저가 HTML을 렌더링 트리로 구문 분석하는 방법, JavaScript 이벤트 루프가 마이크로태스크와 매크로태스크를 처리하는 방법, React의 조정자가 가상 DOM 트리를 비교하는 방법, V8 JIT 컴파일 핫 기능(최신 웹 개발의 이면에 있는 정확한 파이프라인, 데이터 구조 및 스케줄링 메커니즘).
1. 브라우저 렌더링 파이프라인: 중요 경로¶
flowchart LR
subgraph "Critical Rendering Path"
HTML["HTML bytes\n(network)"]
DOM["DOM Tree\n(tokenizer → parser\n→ element nodes)"]
CSSOM["CSSOM Tree\n(parallel CSS parse\n→ style rules)"]
RENDER["Render Tree\n(DOM + CSSOM merged\ninvisible nodes excluded)"]
LAYOUT["Layout (Reflow)\n(box model compute:\nwidth, height, position\nCPU-intensive)"]
PAINT["Paint\n(draw to layers:\nbackgrounds, borders, text\nrasterize to pixels)"]
COMPOSITE["Composite\n(GPU: merge layers\nwith transforms/opacity\nGPU-accelerated)"]
HTML --> DOM
HTML --> CSSOM
DOM --> RENDER
CSSOM --> RENDER
RENDER --> LAYOUT --> PAINT --> COMPOSITE
end
HTML 토크나이저 상태 머신¶
HTML 토크나이저는 ~80개의 상태를 갖는 상태 머신입니다. 상황에 맞는 규칙으로 인해 HTML을 정규식으로 구문 분석할 수는 없습니다.
stateDiagram-v2
[*] --> Data: Initial state
Data --> TagOpen: < character
TagOpen --> StartTagName: [a-z]
TagOpen --> EndTagOpen: /
StartTagName --> BeforeAttributeName: whitespace
StartTagName --> Data: >
BeforeAttributeName --> AttributeName: [a-z]
AttributeName --> BeforeAttributeValue: =
BeforeAttributeValue --> AttributeValueDoubleQuoted: "
AttributeValueDoubleQuoted --> AfterAttributeValue: "
AfterAttributeValue --> Data: >
Data --> RCDATA: title/textarea start tag
RCDATA --> Data: matching end tag
스크립트 차단: 파서가 <script> 태그(async/defer 없음)를 발견하면 **HTML 구문 분석을 일시 중지**하고 스크립트를 실행한 다음(DOM을 수정할 수 있음) 다시 시작합니다. 이것이 바로 <body> 끝에 있는 <script>이 성능에 중요한 이유입니다.
2. 자바스크립트 이벤트 루프: 마이크로태스크 vs 매크로태스크¶
flowchart TD
subgraph "V8 Event Loop Phases"
CALL["Call Stack\n(synchronous execution)"]
MICRO["Microtask Queue\nPromise.then, queueMicrotask,\nMutationObserver callbacks"]
MACRO["Macrotask Queue\nsetTimeout, setInterval,\nI/O callbacks, UI events"]
RAF["requestAnimationFrame\n(before next paint)"]
RENDER["Render Pipeline\n(layout + paint + composite)"]
CALL -->|stack empty| MICRO
MICRO -->|drain ALL microtasks| MICRO
MICRO -->|queue empty| RAF
RAF --> RENDER
RENDER --> MACRO
MACRO -->|pick one| CALL
end
마이크로태스크 기아의 예¶
// This BLOCKS rendering indefinitely:
function infiniteMicrotasks() {
Promise.resolve().then(infiniteMicrotasks);
// Microtask queue never empties → RAF never runs → page freezes
}
// Correct: yield to macrotask queue
function yieldToRender(callback) {
setTimeout(callback, 0); // or: scheduler.postTask()
}
내부 상태 머신 약속¶
stateDiagram-v2
[*] --> Pending: Promise created
Pending --> Fulfilled: resolve(value) called
Pending --> Rejected: reject(reason) called
Fulfilled --> [*]: .then(onFulfilled) queues microtask
Rejected --> [*]: .catch(onRejected) queues microtask
note right of Fulfilled: State is immutable\nonce settled
3. V8 JIT 컴파일 파이프라인¶
flowchart LR
subgraph "V8 Compiler Tiers"
SRC["JavaScript source"]
PARSE["Parser → AST\n(Abstract Syntax Tree)"]
IGN["Ignition Interpreter\n(bytecode — executes immediately)\nCollects type feedback"]
SPARK["Sparkplug Compiler\n(fast baseline JIT\nbytecode→machine code\nno optimization)\n~10ms warm-up"]
TURBO["TurboFan Optimizing JIT\n(triggered when function 'hot')\nSpeculative optimization\nbased on type feedback"]
DEOPT["Deoptimization\n(if assumption violated:\ne.g., type changes)\nFall back to Ignition"]
SRC --> PARSE --> IGN --> SPARK --> TURBO
TURBO --> DEOPT --> IGN
end
TurboFan 추측 최적화¶
sequenceDiagram
participant JS as Hot Function: add(a, b) = a + b
participant TF as TurboFan
participant IC as Inline Cache
Note over IC: Called 10000× with integers
IC->>TF: Type feedback: a=Smi, b=Smi (small ints)
Note over TF: Speculate: always integers
Note over TF: Emit MOV rax,[a], ADD rax,[b], RET
Note over TF: Insert guard: CHECK type(a)==Smi
TF-->>JS: Optimized machine code
Note over JS: Called with add("hello", 5)
Note over JS: Type guard FAILS (a is String!)
JS->>TF: DEOPTIMIZE
TF-->>JS: Back to Ignition bytecode
Note over IC: Type feedback now: String|Smi\nRe-optimize with union type (slower)
숨겨진 클래스(모양/지도)¶
V8은 동일한 속성 레이아웃을 가진 객체에 숨겨진 클래스(모양)를 할당하여 속성 액세스를 최적화합니다.
flowchart LR
subgraph "Object Shape Transitions"
C0["Shape C0: {}"]
C1["Shape C1: {x: offset=0}"]
C2["Shape C2: {x: offset=0, y: offset=8}"]
C0 -->|obj.x = 5| C1
C1 -->|obj.y = 10| C2
end
subgraph "Shape Sharing (Fast)"
P1["point1 = {x:1, y:2}\n→ Shape C2"]
P2["point2 = {x:3, y:4}\n→ Shape C2 (same!)"]
FAST["Property read point1.x:\n lookup offset[C2.x] = 0\n read memory[ptr+0]\n O(1) — no hash table!"]
P1 --> FAST
P2 --> FAST
end
subgraph "Shape Miss (Slow)"
P3["point3 = {y:2, x:1}\n→ different shape C3!\n(different insertion order)"]
SLOW["Cannot share shape with C2\nSeparate shape chain"]
end
4. React 조정: 파이버 아키텍처¶
flowchart TD
subgraph "React Fiber Tree"
WIP["Work-In-Progress Tree\n(being built/updated)"]
CURR["Current Tree\n(on screen)"]
ALT["alternate pointer:\nFiber nodes recycled\nbetween current and WIP"]
WIP <--> ALT
CURR <--> ALT
end
subgraph "Fiber Node Structure"
FN["Fiber {\n type: 'div' | ComponentFn\n key: string\n stateNode: DOM node | class instance\n child: → first child fiber\n sibling: → next sibling fiber\n return: → parent fiber\n pendingProps: {}\n memoizedProps: {}\n memoizedState: Hook list\n effectTag: UPDATE|PLACEMENT|DELETION\n updateQueue: linked list of updates\n}"]
end
조정: 차이 알고리즘¶
sequenceDiagram
participant App as State Update: setCount(5)
participant Sched as React Scheduler
participant Render as Render Phase (pure)
participant Commit as Commit Phase (DOM)
App->>Sched: scheduleUpdateOnFiber()
Note over Sched: Assign priority (lane)\nScheduler: postMessage for async work\nor synchronous for urgent updates
Sched->>Render: beginWork(fiber)\nTop-down tree traversal\n(can be paused/resumed!)
Note over Render: Compare new element type + key:\n same type → update props\n different type → unmount + remount\n list: key matching for minimal DOM ops
Render->>Render: completeWork(fiber)\nCollect effectList\n(mutations needed)
Render->>Commit: Synchronous (cannot pause)\ncommitMutationEffects: apply DOM changes\ncommitLayoutEffects: run useLayoutEffect\ncommitPassiveEffects: run useEffect (async)
동시 모드: 시간 분할¶
React 18 동시 모드는 **스케줄러**를 사용하여 렌더링 작업을 5ms 조각으로 나눕니다.
flowchart TD
WORK["Rendering 1000 components\n~50ms total work"]
SLICE1["Work slice 1: 5ms\n→ yield to browser"]
INPUT["Browser: handle user input\n(0.5ms — stays responsive!)"]
SLICE2["Work slice 2: 5ms"]
PAINT["Browser: paint frame\n(16ms budget kept!)"]
CONT["Continue until complete\n(10 slices × 5ms)"]
WORK --> SLICE1 --> INPUT --> SLICE2 --> PAINT --> CONT
5. 가상 DOM 차이점: 주요 알고리즘¶
flowchart TD
subgraph "Tree Diff O(N) Heuristics"
H1["Heuristic 1: Different root type\n→ tear down entire subtree\n→ don't recurse into it"]
H2["Heuristic 2: Same type element\n→ update attributes only\n→ recurse into children"]
H3["Heuristic 3: key prop on lists\n→ match by key across renders\n→ minimal moves/inserts/deletes"]
end
subgraph "List Reconciliation with Keys"
OLD["Old: [A(key=1), B(key=2), C(key=3)]"]
NEW["New: [C(key=3), A(key=1), B(key=2)]"]
DIFF["Without keys: 3 updates (wrong)
With keys:\n C: move to position 0\n A: move to position 1\n B: move to position 2\n= 2 DOM moves (efficient)"]
OLD --> DIFF
NEW --> DIFF
end
6. CSS 캐스케이드 및 특이성 계산¶
flowchart TD
subgraph "Cascade Order (later wins at same specificity)"
C1["User-agent stylesheet\n(browser defaults)"]
C2["User stylesheet\n(accessibility overrides)"]
C3["Author stylesheets\n(your CSS files)"]
C4["Author !important"]
C5["User !important"]
C6["User-agent !important"]
C1 --> C2 --> C3 --> C4 --> C5 --> C6
end
subgraph "Specificity Calculation (a,b,c,d)"
S1["(1,0,0,0) — inline style"]
S2["(0,1,0,0) per ID selector\n#header → (0,1,0,0)"]
S3["(0,0,1,0) per class/attr/pseudo-class\n.active → (0,0,1,0)\n[type='text'] → (0,0,1,0)"]
S4["(0,0,0,1) per element/pseudo-element\ndiv → (0,0,0,1)\np::first-line → (0,0,0,2)"]
EXAMPLE["#nav .item:hover span\n= (0,1,0,0)+(0,0,1,0)+(0,0,1,0)+(0,0,0,1)\n= (0,1,2,1)"]
S1 --> EXAMPLE
S2 --> EXAMPLE
S3 --> EXAMPLE
S4 --> EXAMPLE
end
7. 웹 성능: 중요한 리소스 로딩¶
sequenceDiagram
participant Browser as Browser
participant Server as Server
Browser->>Server: GET / (HTML)
Server-->>Browser: HTML (first byte ~50ms)
Note over Browser: Parse HTML → discover resources
par Parallel resource loading
Browser->>Server: GET /style.css (render-blocking!)
Browser->>Server: GET /bundle.js (defer)
Browser->>Server: GET /hero.jpg (preload)
end
Server-->>Browser: style.css
Note over Browser: CSSOM built → unblock render
Server-->>Browser: First chunk of bundle.js
Note over Browser: FCP (First Contentful Paint) possible now
Server-->>Browser: hero.jpg
Note over Browser: LCP (Largest Contentful Paint)
Server-->>Browser: bundle.js complete
Note over Browser: TTI (Time to Interactive)\nJS parsed + executed
핵심 웹 바이탈 내부 트리거¶
| 미터법 | 트리거 | 측정 |
|---|---|---|
| LCP | 가장 큰 이미지/텍스트 블록이 그려짐 | PerformanceObserver 유형 largest-contentful-paint |
| FID/INP | 입력 이벤트 → 브라우저 응답 지연 | PerformanceEventTiming.processingStart - startTime |
| CLS | 레이아웃 변경: 사용자 상호 작용 없이 요소가 이동합니다 | LayoutShift.value = impact_fraction × distance_fraction |
8. 서비스 워커: 가로채기 내부 요소 가져오기¶
sequenceDiagram
participant Page as Web Page
participant SW as Service Worker\n(separate thread)
participant Cache as Cache Storage API
participant Net as Network
Page->>SW: fetch('/api/data') [intercepted]
Note over SW: self.addEventListener('fetch', event)
SW->>Cache: caches.match(request)
Cache-->>SW: Cache HIT → cached response
SW-->>Page: Serve from cache (offline works!)
Note over SW: Cache MISS scenario:
SW->>Net: fetch(request) [network request]
Net-->>SW: Network response
SW->>Cache: cache.put(request, response.clone())
SW-->>Page: Network response
서비스 워커 수명 주기 — 페이지와 별도로 페이지 로드 시 지속됩니다.
9. WebAssembly: 실행 모델¶
flowchart TD
subgraph "WebAssembly Execution Pipeline"
C["C/C++/Rust source"]
WASM["WebAssembly binary\n(.wasm)\nstructured binary format:\nmodule, functions, tables, memory"]
VALIDATE["Browser validates WASM\n(type-check in O(N) single pass)\nSafer than JS eval"]
JIT["JIT compile to machine code\n(WASM types are explicit\n→ simpler/faster than JS JIT\n~5% of native speed achievable)"]
EXEC["Execute in sandboxed linear memory\n(no pointers outside WASM.memory\ncannot access browser internals)"]
C --> WASM --> VALIDATE --> JIT --> EXEC
end
subgraph "WASM Linear Memory"
MEM["Single contiguous ArrayBuffer\n[0..n MB]\nmanually managed by WASM\n(malloc from emscripten/wasi)\nJS can read/write same buffer\n(shared memory via SharedArrayBuffer)"]
end
10. HTTP/2 다중화 및 HOL 차단¶
sequenceDiagram
participant Browser as Browser
participant H2 as HTTP/2 Server
Note over Browser,H2: Single TCP connection, multiple streams
Browser->>H2: HEADERS frame (stream 1): GET /style.css\n HEADERS frame (stream 3): GET /bundle.js\n HEADERS frame (5): GET /image.jpg\n (all sent in parallel, same connection!)
H2->>Browser: DATA frame (stream 3): 16KB of bundle.js
H2->>Browser: DATA frame (stream 1): complete style.css\n DATA frame (stream 3): next 16KB bundle.js
H2->>Browser: DATA frame (stream 5): image.jpg
Note over Browser,H2: HTTP/2 Head-of-Line still present at TCP level:\n single packet loss stalls ALL streams\nHTTP/3 (QUIC) solves this with\nindependent UDP streams
11. WebSocket: 프레임 프로토콜 내부¶
flowchart LR
subgraph "WebSocket Frame Header"
B0["Byte 0:\n bit 7: FIN (last fragment)\n bit 4-6: RSV1-3 (extensions)\n bit 0-3: opcode\n (0=continuation,1=text,2=binary\n 8=close,9=ping,A=pong)"]
B1["Byte 1:\n bit 7: MASK (client→server must mask)\n bit 0-6: payload_len\n (0-125: actual\n 126: next 2 bytes = real len\n 127: next 8 bytes = real len)"]
MASK["Masking key (4 bytes, if MASK=1)\nXOR with payload bytes cyclically:\n masked[i] = payload[i] XOR key[i%4]\n (prevents proxy cache poisoning)"]
B0 --> B1 --> MASK
end
프론트엔드 아키텍처 요약¶
block-beta
columns 2
block:Rendering
RTree["Render Tree\nDOM+CSSOM merged\nno hidden elements"]
Layout["Layout/Reflow\nbox positions computed\nexpensive on % widths"]
Composite["GPU Compositing\ntransform/opacity free\nlayer promotion: will-change"]
end
block:JavaScript
EventLoop["Event Loop\nmicrotask drain first\nRAF before paint"]
V8JIT["V8 TurboFan\nspeculative optimization\ntype guard deopt"]
React["React Fiber\ninterruptible render\ntime-sliced Concurrent Mode"]
end
block:Network
H2["HTTP/2\nmultiplexed streams\nheader compression HPACK"]
CRP["Critical Render Path\nCSS render-blocking\nJS parser-blocking"]
SW["Service Worker\nfetch interception\noffline caching"]
end
block:Security
CSP["Content Security Policy\nscript-src restrict\nprevents XSS"]
CORS["CORS\npreflight OPTIONS\nAccess-Control headers"]
SameSite["SameSite Cookie\nLax/Strict/None\nCSRF prevention"]
end
설계적 고민¶
구조와 모델링¶
프론트엔드 아키텍처의 근본적 구조 결정은 **렌더링 전략 선택**입니다. CSR(Client-Side Rendering), SSR(Server-Side Rendering), SSG(Static Site Generation), ISR(Incremental Static Regeneration)은 각각 다른 성능 프로파일과 개발 복잡도를 가집니다.
CSR은 초기 로딩이 느리지만 이후 페이지 전환이 빠르고, SSR은 TTFB(Time to First Byte)가 빠르나 서버 부하가 증가합니다. SSG는 빌드 타임에 모든 페이지를 생성하여 CDN에서 직접 제공하므로 가장 빠르지만, 데이터 변경 시 재빌드가 필요합니다. ISR은 SSG의 장점을 유지하면서 revalidate 주기로 페이지를 갱신합니다.
flowchart TD
subgraph "CSR (Create React App)"
CSR_REQ["브라우저 요청"] --> CSR_HTML["빈 HTML + JS 번들\n(FCP 지연)"]
CSR_HTML --> CSR_JS["JS 다운로드/파싱/실행\n(TTI 지연)"]
CSR_JS --> CSR_API["API 호출 → 데이터 fetch"]
CSR_API --> CSR_RENDER["DOM 렌더링 완료\n(LCP 매우 늦음)"]
end
subgraph "SSR (Next.js getServerSideProps)"
SSR_REQ["브라우저 요청"] --> SSR_SRV["서버에서 데이터 fetch\n+ HTML 생성"]
SSR_SRV --> SSR_HTML["완성된 HTML 전송\n(FCP 빠름)"]
SSR_HTML --> SSR_HYD["Hydration: JS 이벤트 바인딩\n(TTI = FCP + Hydration)"]
end
subgraph "SSG + ISR (Next.js)"
SSG_BUILD["빌드 타임 HTML 생성"] --> SSG_CDN["CDN 캐시 배포\n(TTFB 최소)"]
SSG_CDN --> SSG_REQ["사용자 요청 → CDN 직접 응답"]
SSG_REQ -->|"stale-while-revalidate"| SSG_REVAL["백그라운드 재생성\n(revalidate: 60s)"]
end
상태 관리 아키텍처 또한 핵심 구조 결정입니다. 상태를 어디에 위치시킬 것인지에 따라 컴포넌트 간 결합도, 디버깅 용이성, 성능이 크게 달라집니다. 로컬 상태(useState)는 단일 컴포넌트 내에서만 유효하고, 전역 상태(Redux/Zustand)는 앱 전체에서 공유되며, 서버 상태(React Query/SWR)는 캐시 무효화와 동기화 전략이 핵심입니다.
트레이드오프와 의사결정¶
**마이크로 프론트엔드 vs 모놀리식 SPA**는 조직 규모와 배포 주기에 따른 핵심 의사결정입니다. 마이크로 프론트엔드는 팀 독립성과 독립 배포를 가능하게 하지만, 공유 상태 관리, 스타일 충돌, 번들 중복, 라우팅 통합 등의 복잡도가 크게 증가합니다.
flowchart TD
subgraph "모놀리식 SPA"
MONO_BUILD["단일 빌드 파이프라인"] --> MONO_BUNDLE["하나의 번들\n공유 의존성 최적화"]
MONO_BUNDLE --> MONO_DEPLOY["전체 배포\n단일 팀 조율 필요"]
MONO_DEPLOY --> MONO_PROS["장점:\n- 코드 공유 용이\n- 일관된 UX\n- 번들 최적화"]
MONO_DEPLOY --> MONO_CONS["단점:\n- 팀 간 배포 충돌\n- 빌드 시간 증가\n- 전체 장애 위험"]
end
subgraph "마이크로 프론트엔드"
MFE_TEAM1["팀 A: 상품 검색\n독립 빌드/배포"]
MFE_TEAM2["팀 B: 결제\n독립 빌드/배포"]
MFE_TEAM3["팀 C: 사용자 관리\n독립 빌드/배포"]
MFE_SHELL["App Shell\nModule Federation\n라우팅 통합"]
MFE_TEAM1 --> MFE_SHELL
MFE_TEAM2 --> MFE_SHELL
MFE_TEAM3 --> MFE_SHELL
MFE_SHELL --> MFE_PROS["장점:\n- 독립 배포\n- 기술 스택 자유\n- 장애 격리"]
MFE_SHELL --> MFE_CONS["단점:\n- 번들 중복(React 2벌)\n- 공유 상태 어려움\n- 스타일 충돌"]
end
**번들 최적화 전략**에서의 트레이드오프도 중요합니다. 코드 스플리팅은 초기 로딩을 줄이지만 라우트 전환 시 추가 네트워크 요청이 발생합니다. 트리 쉐이킹은 사용하지 않는 코드를 제거하지만 사이드 이펙트가 있는 모듈은 제거할 수 없습니다. 레이지 로딩은 필요한 시점에 로드하지만 사용자 경험에 지연을 줄 수 있어 prefetch 힌트와 함께 사용해야 합니다.
| 전략 | 초기 로딩 | 후속 탐색 | 개발 복잡도 | 적합한 케이스 |
|---|---|---|---|---|
| 코드 스플리팅 | ✓ 감소 | △ 추가 요청 | 중간 | 대규모 SPA |
| 트리 쉐이킹 | ✓ 감소 | 영향 없음 | 낮음 | ESM 기반 라이브러리 |
| 레이지 로딩 | ✓ 대폭 감소 | △ 지연 가능 | 중간 | 비핵심 기능 |
| Prefetch/Preload | 영향 없음 | ✓ 빠름 | 낮음 | 예측 가능한 네비게이션 |
리팩토링과 설계 원칙¶
프론트엔드 리팩토링의 핵심 원칙은 **관심사 분리(Separation of Concerns)**입니다. UI 로직, 비즈니스 로직, 데이터 접근 로직을 명확히 분리하면 테스트와 유지보수가 용이해집니다. Custom Hook 패턴은 React에서 이 원칙을 실현하는 핵심 수단입니다.
flowchart TD
subgraph "리팩토링 전: 거대 컴포넌트"
BEFORE["ProductPage 컴포넌트\n- API 호출 로직\n- 상태 관리 로직\n- 에러 처리 로직\n- UI 렌더링 로직\n- 이벤트 핸들러\n(500줄 이상)"]
end
subgraph "리팩토링 후: 관심사 분리"
HOOK["useProduct() Hook\n- API 호출 + 캐싱\n- 로딩/에러 상태\n- 낙관적 업데이트"]
LOGIC["useProductLogic() Hook\n- 비즈니스 규칙\n- 유효성 검증\n- 상태 변환"]
UI["ProductPage 컴포넌트\n- 순수 UI 렌더링만\n- Props 기반\n(100줄 이하)"]
PARTS["하위 컴포넌트들\n- ProductCard\n- PriceDisplay\n- ReviewList"]
HOOK --> UI
LOGIC --> UI
UI --> PARTS
end
**웹 컴포넌트 vs 프레임워크 컴포넌트**도 설계 원칙과 연관된 결정입니다. 웹 컴포넌트(Custom Elements + Shadow DOM)는 웹 표준이므로 프레임워크에 종속되지 않지만, 생태계와 DX(개발자 경험)가 React/Vue에 비해 부족합니다. 디자인 시스템처럼 프레임워크 간 공유가 필요한 경우 웹 컴포넌트가 적합하고, 특정 프레임워크 내 생산성이 우선이면 프레임워크 컴포넌트가 유리합니다.
**성능 최적화 리팩토링**에서는 측정 기반 접근이 필수입니다. Lighthouse, Web Vitals(LCP, FID, CLS) 메트릭을 기준으로 병목을 식별하고, 가장 임팩트가 큰 부분부터 최적화합니다. 무분별한 React.memo나 useMemo는 오히려 메모리 사용량을 증가시킬 수 있으므로, 프로파일링으로 실제 렌더링 병목을 확인한 후 적용해야 합니다.
디자인 패턴 적용¶
프론트엔드에서 가장 많이 사용되는 디자인 패턴은 Compound Component, Render Props, Higher-Order Component(HOC), Custom Hook 패턴입니다. 최근에는 Custom Hook이 HOC와 Render Props를 대체하는 추세입니다.
flowchart TD
subgraph "프론트엔드 디자인 패턴 진화"
MIXIN["Mixin (과거)\n이름 충돌\n암묵적 의존성"] -->|"대체"| HOC["HOC 패턴\nwithAuth(Component)\nwithRouter(Component)\n중첩 시 Props 충돌"]
HOC -->|"대체"| RENDER["Render Props\nchildren as function\n유연하지만 콜백 지옥"]
RENDER -->|"대체"| HOOKS["Custom Hooks\nuseAuth(), useRouter()\n조합 용이, 테스트 용이"]
end
subgraph "Compound Component 패턴"
TAB["Tab.Root"] --> TAB_LIST["Tab.List"]
TAB --> TAB_PANELS["Tab.Panels"]
TAB_LIST --> TAB_ITEM1["Tab.Item\nactive 상태 공유"]
TAB_LIST --> TAB_ITEM2["Tab.Item"]
TAB_PANELS --> TAB_PANEL1["Tab.Panel\nContext로 상태 공유"]
TAB_PANELS --> TAB_PANEL2["Tab.Panel"]
end
**Container/Presenter 패턴**은 데이터 로직과 UI 렌더링을 분리하는 고전적 패턴입니다. Container가 데이터를 가져와 Presenter에 Props로 전달하면, Presenter는 순수하게 UI만 담당합니다. 이 패턴은 Storybook에서의 컴포넌트 격리 테스트를 매우 용이하게 만듭니다.
**Error Boundary 패턴**은 React에서 컴포넌트 트리의 에러를 격리하는 패턴입니다. 개별 위젯의 에러가 전체 페이지를 다운시키지 않도록, 기능 단위로 Error Boundary를 배치하고 Fallback UI를 제공합니다. 이는 마이크로 프론트엔드에서 특히 중요하며, 독립 배포되는 각 마이크로 앱의 장애가 호스트 앱에 전파되지 않도록 보장합니다.
연습 문제¶
1. 시스템 구조와 모델링¶
문제 1-1. 사용자가 브라우저에 URL을 입력하면, 브라우저는 HTML 파싱 → DOM 구성 → CSSOM 생성 → Render Tree 결합 → Layout → Paint → Composite 단계를 거쳐 화면을 렌더링합니다. 이 파이프라인에서 <script> 태그가 DOM 파싱을 블록하는 이유를 설명하고, async와 defer 속성이 각각 이 블로킹 동작을 어떻게 변경하는지 비교하세요. CSS 파일의 로딩이 JavaScript 실행을 블록하는 상황(render-blocking vs parser-blocking)도 함께 분석하세요.