Skip to content

필사 모드: React Fiber 내부 완전 가이드 2025: Reconciler, Scheduler, Concurrent Rendering, Hooks 심층 분석

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며: 가장 많이 쓰이는 라이브러리의 내부

React의 지배력

2013년 Facebook이 공개한 React는 **프런트엔드의 표준**이 되었다:

- **npm 다운로드**: 주당 수천만 건.

- **Stack Overflow**: 가장 많이 질문되는 프레임워크.

- **사용하는 사이트**: 수백만 개.

- **기업 채택**: Meta, Netflix, Airbnb, Uber, Microsoft, 그리고 수많은 스타트업.

**하지만** 대부분의 React 개발자는 **내부를 모른다**. `useState`가 어떻게 작동하는지, `useEffect`의 cleanup이 언제 실행되는지, concurrent mode가 정확히 무엇을 하는지.

이 지식 없이도 React를 쓸 수 있다. 하지만 **깊은 이해**가 있으면:

- 디버깅이 쉬워진다.

- 성능 최적화가 **정확해진다**.

- 버그 패턴을 **미리 피한다**.

- 더 좋은 코드를 쓴다.

이 글에서 다룰 것

1. **가상 DOM (Virtual DOM)**: React의 기본 아이디어.

2. **Reconciliation**: 어떻게 차이를 찾나.

3. **Fiber 아키텍처**: React 16의 대변화.

4. **Scheduler**: 언제 무엇을 할지.

5. **Concurrent Rendering (React 18)**: Time slicing, Suspense.

6. **Hooks의 내부**: 어떻게 클로저로 구현되나.

7. **React 19 **: Compiler, Server Components.

8. **성능 최적화**: useMemo, useCallback, Memoization.

1. Virtual DOM: React의 시작점

DOM의 문제

**DOM (Document Object Model)**: 브라우저가 HTML을 표현하는 객체 트리.

**DOM 조작의 비용**:

- **느림**: CSS 재계산, 레이아웃, 페인트.

- **직접 조작 복잡**: `document.getElementById`, `innerHTML` 등.

- **State 관리 어려움**: UI와 state 동기화.

**2013년 이전**:

- jQuery가 주류.

- 직접 DOM 조작.

- State를 DOM에 저장 (혼란).

React의 아이디어

**"UI를 함수처럼 다루자"**:

$$UI = f(state)$$

- State가 변하면 **새 UI**.

- 사용자는 UI 선언만, **어떻게 바꿀지**는 React가.

**Virtual DOM**이 이를 가능하게 한다:

1. **가상 DOM 트리**: JavaScript 객체로 UI 표현.

2. State 변경 시 **새 가상 DOM** 생성.

3. **이전 가상 DOM과 비교** (diffing).

4. **차이만 실제 DOM에** 적용.

예시

function Counter() {

const [count, setCount] = useState(0);

return (

);

}

**처음**:

Virtual DOM:

div

h1: "Count: 0"

button: "+"

이것을 **실제 DOM**으로 렌더.

**클릭 후**:

새 Virtual DOM:

div

h1: "Count: 1" ← 변경

button: "+" ← 동일

**Diff**: `h1`의 textContent만 바뀜.

**실제 DOM 업데이트**: `h1.textContent = "Count: 1"` 만.

**Result**: 효율적, 선언적, 관리 쉬움.

JSX

**JSX**: React의 "HTML 같은" 문법.

const element = <h1>Hello</h1>;

Babel이 다음으로 컴파일:

const element = React.createElement('h1', null, 'Hello');

**React.createElement의 결과**:

{

type: 'h1',

props: { children: 'Hello' },

key: null,

ref: null,

// ...

}

이것이 **virtual DOM 노드**. 평범한 JavaScript 객체.

2. Reconciliation: 어떻게 비교하나

완벽한 diff는 비싸다

**두 트리를 완벽히 비교**하는 알고리즘: **O(n^3)**. 수천 노드가 있으면 **수 초**.

**React의 가정들**:

**1. 다른 타입 → 완전히 다른 트리**:

// Before

// After

`div` vs `span`. React는 **전체 트리 재구성**. Counter도 unmount 후 새로 mount.

**왜?**: 그런 변화는 드물고, 정확한 diff는 너무 비쌈.

**2. Key로 리스트 항목 식별**:

{items.map(item => <li key={item.id}>{item.name}</li>)}

Key가 **안정적 ID** 역할. React가 항목을 **추적** 가능.

**Key 없으면**: 인덱스 기반. 삽입/삭제 시 문제.

Diff Algorithm의 복잡도

위 가정들로 **O(n)** 에 근접. 실용적.

Reconciliation 단계

**1. Type 비교**:

- 같은 type: 속성만 업데이트.

- 다른 type: 전체 재구성.

**2. Props 비교**:

- 변경된 props만 적용.

- 이벤트 리스너 추가/제거.

**3. Children 비교**:

- Key 기반 매칭.

- 순서 변경 감지.

예시: Key의 중요성

**Key 없음**:

// 앞에 추가:

React는 **인덱스 기반**:

- 0: Apple → Cherry (update)

- 1: Banana → Apple (update)

- 2: (새로 추가) Banana

**3개 update/create**.

**Key 있음**:

// 앞에 추가:

React는 **key 기반**:

- "c": 새로 생성.

- "a": 이동만.

- "b": 이동만.

**1개만 create**. 훨씬 효율적.

**특히 중요한 경우**:

- 큰 리스트.

- 내부 state가 있는 컴포넌트.

- Animation.

3. Fiber 아키텍처 (React 16)

기존 Reconciler의 문제

**React 15 이전의 "Stack Reconciler"**:

render() {

// 1. 전체 트리 diff

// 2. 전체 트리 업데이트

// 3. 완료까지 차단

}

**문제**: **중단 불가**. 큰 트리 업데이트 시 **수백 ms 블로킹**. 애니메이션 끊김.

**브라우저 프레임**: **16.67 ms** (60fps). 그 안에 모든 작업 끝내야 부드러움.

React 15: 큰 업데이트면 16ms 초과 → **프레임 드롭**.

Fiber의 해결

**React 16** (2017): **Fiber Reconciler** 도입.

**핵심 변경**:

- **작업을 작은 단위로** 쪼갬.

- 각 단위 실행 후 **브라우저에 양보**.

- 우선순위 높은 작업 (사용자 입력 등)을 **먼저**.

- **중단과 재개 가능**.

**결과**: 부드러운 UI, 응답성 향상.

Fiber란

**Fiber**: 작업 단위를 표현하는 **JavaScript 객체**.

{

type: 'div',

key: null,

props: { ... },

stateNode: (실제 DOM 노드),

// Tree 구조

return: parent_fiber,

child: first_child_fiber,

sibling: next_sibling_fiber,

// 작업 정보

pendingProps: newProps,

memoizedProps: oldProps,

effectTag: Update,

// Alternate: 이전 버전

alternate: previousFiber,

// ... 더 많은 필드

}

**Fiber 트리**는 virtual DOM 트리에 대응하지만, **더 많은 정보**.

Fiber Tree 구조

**Linked list** 구조:

- `return`: 부모.

- `child`: 첫 자식.

- `sibling`: 다음 형제.

트리 순회가 **iterative** 가능. Stack 없이.

A

/ | \

B C D

A.child = B

B.sibling = C

C.sibling = D

B.return = A

C.return = A

D.return = A

Double Buffering

**두 개의 Fiber tree**:

**current**: 현재 화면에 반영된 트리.

**workInProgress**: 작업 중인 트리.

**작동**:

1. State 변경.

2. **workInProgress 트리** 생성 (current를 복사).

3. 변경 사항 적용.

4. 완료 시 **swap**: workInProgress가 current가 됨.

5. 실제 DOM 업데이트.

**이점**:

- 작업 중에도 현재 UI 보존.

- Commit을 원자적으로.

- Rollback 가능.

Fiber Phases

**1. Render Phase** (중단 가능):

- Fiber 트리 생성/업데이트.

- Diff 계산.

- Effects 수집.

- **부작용 없음** (DOM 수정 안 함).

- 언제든 중단, 재개, 폐기 가능.

**2. Commit Phase** (동기):

- 실제 DOM 변경 적용.

- Side effects (useEffect 등) 실행.

- **중단 불가**.

- **빠르게** 완료되어야.

Render는 여러 번 실행될 수 있지만, commit은 **한 번**. 이것이 "render는 순수해야 한다"는 이유.

4. Scheduler: 언제 무엇을 할지

Scheduler의 역할

**Scheduler**: 작업의 **우선순위**와 **타이밍**을 관리.

**목표**:

- 높은 우선순위 작업 **먼저**.

- 브라우저가 할 일 있으면 **양보**.

- 60fps 유지.

우선순위 레벨

React 18의 priorities:

1. **Immediate**: 즉시. 동기적으로.

2. **User-blocking**: 250 ms 이내. 사용자 입력 등.

3. **Normal**: 5 초 이내. 일반 업데이트.

4. **Low**: 10 초 이내. 저우선순위.

5. **Idle**: 시간 남을 때.

Time Slicing

**작업을 조각내서** 실행:

work | check time | if > 5ms, yield | work | check...

각 조각 후 브라우저에 제어권. 브라우저가 **이벤트 처리, 페인트** 등 수행 후 돌아옴.

requestIdleCallback?

초기 계획은 `requestIdleCallback` 사용. 하지만:

- **지원 부족** (Safari).

- **지연 너무 김**.

대신 **`MessageChannel`** 기반 자체 스케줄러:

const channel = new MessageChannel();

channel.port1.onmessage = performWorkUntilDeadline;

channel.port2.postMessage(null); // 다음 틱에 실행

**5ms** 기본 deadline. 각 조각 후 deadline 확인.

예시

function App() {

const [query, setQuery] = useState('');

const [list, setList] = useState(generateLargeList());

// 비싼 필터링

const filtered = useMemo(() =>

list.filter(item => item.name.includes(query)),

[list, query]

);

return (

<>

</>

);

}

**문제**: 각 글자 입력마다:

1. Input 업데이트 (즉시 원함).

2. 큰 리스트 re-render (느림).

**React 17 이전**: 둘 다 같은 우선순위. Input이 버벅임.

**React 18 + useDeferredValue**:

const deferredQuery = useDeferredValue(query);

const filtered = useMemo(() =>

list.filter(item => item.name.includes(deferredQuery)),

[list, deferredQuery]

);

Input update는 **즉시**. 리스트는 **low priority**. Input은 부드럽게, 리스트는 조금 늦게.

5. Concurrent Rendering (React 18)

React 18의 혁명

**2022년 3월**: React 18 출시. **Concurrent Rendering** 기본 활성.

**주요 기능**:

- **Automatic batching**.

- **Transitions** (`useTransition`, `useDeferredValue`).

- **Suspense for data fetching**.

- **Streaming SSR**.

- **New APIs**: `useId`, `useSyncExternalStore`.

Concurrent vs Blocking

**Blocking rendering** (React 17):

User input

setState

Render (중단 불가, 200ms)

↓ (UI 멈춤)

Commit

User sees new UI

**Concurrent rendering** (React 18):

User input

setState (low priority)

Start render (중단 가능)

Another input arrives (high priority)

Abort current render, handle input

Render input result immediately

Resume low priority render

Automatic Batching

**React 17**:

- Event handler 내 여러 setState: **한 번만** re-render.

- 그 외 (promise, setTimeout): **각각** re-render.

// React 17

setTimeout(() => {

setCount(1); // re-render

setFlag(true); // re-render

}, 100);

**React 18**:

- **모든 곳**에서 batching.

- 한 번의 re-render.

// React 18: 한 번의 re-render

setTimeout(() => {

setCount(1);

setFlag(true);

}, 100);

**결과**: 불필요한 re-render 감소.

Transitions

**중요하지 않은 업데이트를 "transition"으로 표시**:

function SearchResults() {

const [query, setQuery] = useState('');

const [results, setResults] = useState([]);

const [isPending, startTransition] = useTransition();

function handleChange(e) {

setQuery(e.target.value); // 높은 우선순위 (input)

startTransition(() => {

// 낮은 우선순위 (결과)

setResults(computeExpensiveResults(e.target.value));

});

}

return (

<>

{isPending && <Spinner />}

</>

);

}

**효과**:

- Input update **즉시**.

- Results computation 백그라운드.

- `isPending` 으로 "계산 중" 상태 표시.

- 사용자가 계속 타이핑 가능.

Suspense

**Suspense**: "로딩 상태를 선언적으로".

**UserProfile**이 데이터를 fetch하는 동안 **fallback** 표시.

**이전**: 각 컴포넌트가 자기 로딩 상태 관리. 복잡, inconsistent.

**Suspense**: **컴포넌트 트리** 수준에서 로딩. 일관된 UX.

Streaming SSR

서버에서 HTML을 **스트리밍**으로:

- Skeleton 먼저 전송.

- 데이터 fetch 완료되면 **stream**으로 추가.

- **Progressive rendering**.

**Next.js 13+** 가 이 기능 적극 활용.

6. Hooks의 내부 구현

Hook이란

**Hooks** (React 16.8, 2019):

function Counter() {

const [count, setCount] = useState(0);

useEffect(() => {

document.title = `Count: ${count}`;

}, [count]);

return <button onClick={() => setCount(count + 1)}>{count}</button>;

}

**함수 컴포넌트**에서 state, lifecycle 사용 가능.

**왜 혁명적**:

- 클래스 없이.

- 재사용 가능한 로직 (custom hooks).

- 더 단순한 코드.

기본 아이디어

**Hook은 어떻게 state를 기억하나**?

**핵심**: **Fiber에 hook 정보 저장**.

각 fiber에 **memoizedState** 필드. Linked list 형태로 여러 hook 저장.

fiber.memoizedState = {

// useState(0)

memoizedState: 0,

queue: updateQueue,

next: {

// useState('')

memoizedState: '',

queue: updateQueue2,

next: {

// useEffect(...)

memoizedState: { deps: [...] },

next: null,

}

}

}

**순서대로** 호출되는 hook이 순서대로 저장됨.

Hook 호출의 규칙

**왜 "Rules of Hooks"가 있는가**:

function Bad() {

if (condition) {

const [a] = useState(1); // ❌ 조건부 호출

}

const [b] = useState(2);

}

**이유**: React는 **호출 순서**로 hook을 매칭. 조건부 호출하면 순서가 바뀜.

**올바른 방법**:

function Good() {

const [a] = useState(1); // 항상 호출

const [b] = useState(2);

if (condition) {

// a 사용

}

}

useState의 구현 (단순화)

let currentFiber = null;

let hookIndex = 0;

function useState(initialValue) {

const fiber = currentFiber;

const index = hookIndex++;

// 이전 값 복원

if (!fiber.memoizedState) {

fiber.memoizedState = [];

}

if (fiber.memoizedState[index] === undefined) {

fiber.memoizedState[index] = initialValue;

}

const setState = (newValue) => {

fiber.memoizedState[index] = newValue;

scheduleRender(fiber);

};

return [fiber.memoizedState[index], setState];

}

**단순화된 모델**:

- Fiber에 hook 배열.

- 각 useState가 인덱스로 접근.

- setState가 scheduler에게 re-render 요청.

**실제 구현**은 linked list + updateQueue. 더 복잡.

useEffect의 구현 (단순화)

function useEffect(callback, deps) {

const fiber = currentFiber;

const index = hookIndex++;

const oldDeps = fiber.memoizedState[index]?.deps;

const hasChanged = !oldDeps || deps.some((d, i) => d !== oldDeps[i]);

if (hasChanged) {

fiber.effects.push({

callback,

cleanup: fiber.memoizedState[index]?.cleanup,

});

fiber.memoizedState[index] = { deps };

}

}

**Effects는 commit phase 후** 실행:

1. Render phase: effect 수집.

2. Commit phase: DOM 업데이트.

3. Effects 실행 (이전 cleanup → 새 effect).

Custom Hooks

**Hook을 재사용**:

function useCounter(initial = 0) {

const [count, setCount] = useState(initial);

const increment = () => setCount(c => c + 1);

const decrement = () => setCount(c => c - 1);

return { count, increment, decrement };

}

function MyComponent() {

const { count, increment } = useCounter(10);

return <button onClick={increment}>{count}</button>;

}

**내부**: Custom hook도 그냥 함수. 내부의 `useState`가 호출자의 fiber에 저장됨.

**순서 중요**: Custom hook 호출 순서가 일정해야.

7. React 19: Compiler와 Server Components

React Compiler (2024)

**React Compiler** (이전 "React Forget"): 자동 메모이제이션.

**문제**:

function MyComponent({ items }) {

const sorted = items.sort(); // 매 render마다 새 배열

return <List items={sorted} />; // List가 매번 re-render

}

**기존 해결**:

const sorted = useMemo(() => items.sort(), [items]);

수동으로 `useMemo`, `useCallback`, `React.memo`. 번거로움.

**React Compiler**: **자동**으로 이들을 추가. 개발자는 평범한 코드 작성.

// 개발자가 쓰는 코드

function MyComponent({ items }) {

const sorted = items.sort();

return <List items={sorted} />;

}

// Compiler가 변환

function MyComponent({ items }) {

const sorted = useMemo(() => items.sort(), [items]);

return <List items={sorted} />;

}

**자동 최적화**. **2024년 실험적, 2025+ 안정화**.

Server Components

**React Server Components (RSC)**: 서버에서 렌더링되는 컴포넌트.

// ServerComponent.jsx (.server.jsx)

export default async function BlogPost({ id }) {

const post = await db.posts.get(id);

return (

);

}

**특징**:

- **서버에서만** 실행.

- JavaScript가 클라이언트로 전송 안 됨.

- DB, 파일 시스템 직접 접근.

- **제로 번들 크기**.

**Client Components**와 혼합 가능:

// 서버

export default function Page() {

return (

);

}

**Next.js 13+**, **Remix** 등이 활용.

React 19 기타

**use() hook**: Promise 직접 언랩.

function Component() {

const data = use(fetchData()); // Suspense 자동

return <div>{data}</div>;

}

**Actions**: 폼과 mutation 단순화.

**useOptimistic**: Optimistic updates.

8. 성능 최적화

React 성능의 기본 원칙

**"과도한 re-render를 피하라"**.

Re-render 발생 조건:

- State 변경.

- Props 변경.

- Context 변경.

- 부모 re-render (기본 동작).

React.memo

**Component memoization**:

const Button = React.memo(({ onClick, children }) => {

console.log('Button rendered');

return <button onClick={onClick}>{children}</button>;

});

**Props가 같으면** re-render 스킵 (shallow comparison).

**주의**:

- 객체, 배열, 함수는 매번 새로 생성 → 항상 다름.

- `useCallback`, `useMemo`와 함께.

useCallback

**함수 참조 안정화**:

const handleClick = useCallback(() => {

doSomething(value);

}, [value]);

`value`가 같으면 같은 함수 참조. Memoized 자식이 re-render 안 함.

useMemo

**값 memoization**:

const expensiveValue = useMemo(() => {

return computeExpensive(data);

}, [data]);

`data`가 같으면 같은 값. 재계산 안 함.

과도한 memoization의 함정

**memo/useCallback/useMemo는 공짜가 아니다**:

- **메모리**: 이전 값 저장.

- **비교 비용**: 의존성 비교.

- **복잡도 증가**: 코드 난해.

**규칙**:

- **기본은 안 씀**.

- 실제 성능 문제 있을 때만 (profiler로 측정).

- 비싼 계산이거나 큰 하위 트리일 때.

**React Compiler (React 19)** 가 이 모든 걸 자동화해 줄 전망.

Key 주의

{items.map((item, index) => (

))}

**Index를 key로**: 삽입/삭제 시 문제. State 섞임.

{items.map((item) => (

))}

**안정적 ID**: 올바름.

State 위치

**State는 **쓰이는 곳에 가깝게**:

// ❌ 상위에 있으면 전체 re-render

function App() {

const [inputValue, setInputValue] = useState('');

return (

);

}

// ✅ 입력 컴포넌트로 분리

function Input() {

const [value, setValue] = useState('');

return <input value={value} onChange={e => setValue(e.target.value)} />;

}

function App() {

return (

);

}

Input의 state 변경이 **Input만** re-render.

Profiler

**React DevTools Profiler**:

- 각 컴포넌트의 render 시간.

- Why did this render?

- Flamegraph.

성능 문제의 **근본 원인**을 찾는 최선의 도구.

9. Context와 상태 관리

Context API

**Prop drilling 해결**:

const ThemeContext = createContext('light');

function App() {

return (

);

}

function Toolbar() {

return <ThemedButton />;

}

function ThemedButton() {

const theme = useContext(ThemeContext);

return <button className={theme}>Click</button>;

}

**Context 변경 시** 모든 consumer re-render.

Context의 함정

**과도 사용**: 모든 걸 context에. Re-render 폭발.

**최적화**:

1. Context를 **세분화**.

2. Memoization.

3. `useSyncExternalStore` (React 18).

**예시 문제**:

`user`만 바뀌어도 **theme 사용자도** re-render.

**해결**:

...

각 context가 독립. 바뀐 것의 consumer만 re-render.

외부 상태 관리

**Redux, Zustand, Jotai, Recoil**: 더 복잡한 state에.

**왜 필요한가**:

- Cross-cutting state.

- 복잡한 데이터 흐름.

- Time-travel debugging.

- Devtools.

**간단한 앱**: Context + hooks 충분.

**복잡한 앱**: 전용 라이브러리.

**최신 트렌드**:

- **Zustand**: 가볍고 단순.

- **Jotai**: Atomic state.

- **TanStack Query**: 서버 상태 (cache, refetch).

Redux는 여전히 쓰이지만 **점점 줄어드는 추세**.

10. 실전 패턴

Component Composition

// ❌ Props drilling

// ✅ Composition

sidebar={<Sidebar />}

content={<Content />}

/>

Context로 필요한 곳에서 받음.

Container/Presentational

**구식 패턴** (Hooks 이전):

- Container: state, logic.

- Presentational: pure render.

**현재**: **Custom hooks**로 대체.

// Custom hook

function useUserData(id) {

const [user, setUser] = useState(null);

useEffect(() => {

fetchUser(id).then(setUser);

}, [id]);

return user;

}

// Component

function UserProfile({ id }) {

const user = useUserData(id);

if (!user) return <Spinner />;

return <h1>{user.name}</h1>;

}

Error Boundaries

**Error를 catch**:

class ErrorBoundary extends Component {

state = { hasError: false };

static getDerivedStateFromError(error) {

return { hasError: true };

}

componentDidCatch(error, errorInfo) {

logError(error, errorInfo);

}

render() {

if (this.state.hasError) {

return <h1>Something went wrong.</h1>;

}

return this.props.children;

}

}

// 사용

**주의**:

- Class component만 가능.

- Event handler, async code는 catch 안 됨.

Compound Components

**공유 state를 가진 관련 컴포넌트들**:

Tabs가 children에 context로 현재 tab 정보 전달.

11. React vs 다른 프레임워크

Vue

**차이점**:

- **Templates**: HTML-like, 더 단순.

- **Reactivity**: Proxy 기반 (자동).

- **Two-way binding**: `v-model`.

- **Composition API**: Hooks와 유사 (Vue 3).

**React와의 차이**:

- 학습 곡선 낮음.

- 더 "자동".

- React보다 더 opinionated.

Svelte

**차이점**:

- **컴파일 타임** 최적화.

- **Virtual DOM 없음**.

- 더 작은 bundle.

- 더 적은 코드.

**Svelte 5** (Runes): Signal-based reactivity.

Solid.js

**차이점**:

- **Fine-grained reactivity**: React의 re-render 없음.

- JSX 유지.

- 매우 빠름.

- Signal 기반.

Qwik

**차이점**:

- **Resumability**: SSR 후 hydration 없음.

- 초기 JavaScript 매우 작음.

- Edge computing 친화적.

React가 여전히 1등인 이유

**장점**:

- **생태계**: 가장 큰 커뮤니티, 가장 많은 라이브러리.

- **검증**: 수많은 대기업이 검증.

- **고용 시장**: 가장 많은 React 개발자.

- **React Native**: 모바일까지.

- **Meta의 지원**: 장기적.

**단점**:

- Virtual DOM 오버헤드.

- Re-render 관리 복잡.

- Bundle 큼.

**선택**: 대부분의 프로젝트에 **React가 안전한 선택**. 새로운 것을 원하면 Solid, Svelte 등 실험.

12. 실전 디버깅과 튜닝

React DevTools

**필수 도구**:

- **Components tab**: 트리 탐색, props/state 확인.

- **Profiler tab**: 성능 분석.

- **Why did this render?**: 원인 파악.

일반적 문제

**1. 과도한 re-render**:

- Props reference 변경.

- Context 오남용.

- State를 너무 상위에.

**해결**: Profiler → React.memo, useCallback, useMemo.

**2. 메모리 누수**:

- useEffect cleanup 안 함.

- Subscription 해제 안 함.

**해결**:

useEffect(() => {

const subscription = subscribe();

return () => subscription.unsubscribe();

}, []);

**3. Stale closures**:

useEffect(() => {

const interval = setInterval(() => {

setCount(count + 1); // count가 항상 0

}, 1000);

return () => clearInterval(interval);

}, []); // [] 때문에 count 고정

**해결**:

setCount(c => c + 1); // functional update

**4. Infinite render loops**:

useEffect(() => {

setData({ ...data, updated: true }); // data 의존성 → 무한

}, [data]);

**해결**: 의존성 배열 확인, 논리 재검토.

Best Practices

1. **Hooks 규칙 준수**: ESLint plugin.

2. **Keys 제대로**: ID 사용, index 피하기.

3. **State 분할**: 세밀하게.

4. **Pure functions**: Side effect는 useEffect로.

5. **Dependencies**: 정확히.

6. **Testing**: React Testing Library.

퀴즈로 복습하기

**A.**

**답**: **중단 가능한 rendering (Interruptible rendering)**.

**상세 설명**:

**Fiber 이전의 문제**:

**Stack Reconciler** (React 15 이하):

- 렌더링이 **재귀 함수 호출**.

- 시작하면 **끝까지 실행**.

- 중단 불가.

render(App)

→ render(Header)

→ render(Logo)

→ render(Nav)

→ render(NavItem) × 100

→ render(Main)

→ render(Article) × 50

→ render(Sidebar)

이 전체가 **하나의 call stack**. 중간에 멈출 수 없음.

**큰 업데이트의 재앙**:

- 1000개 컴포넌트 업데이트.

- 각 컴포넌트 2 ms.

- 총 2000 ms = **2 초**.

- 이 동안 **브라우저 완전 블록**.

- 사용자 입력 무시.

- 애니메이션 끊김.

- 버벅임.

**브라우저의 60 fps**:

- 프레임당 16.67 ms.

- 그 안에 JS + layout + paint + composite.

- React가 16 ms 넘으면 **프레임 드롭**.

- 사용자가 "느림" 인식.

**Fiber의 해결**:

**작업을 작은 단위로 분할**:

- 각 fiber가 하나의 작업 단위.

- 하나 처리 후 **"양보 여부" 확인**.

- 브라우저에 급한 일 있으면 **중단**.

- 나중에 **재개**.

**구현 방식**:

function workLoop(deadline) {

while (deadline.timeRemaining() > 0 && nextUnitOfWork) {

nextUnitOfWork = performUnitOfWork(nextUnitOfWork);

}

if (!nextUnitOfWork) {

// 완료: commit

commitRoot();

} else {

// 중단: 다음 idle 시간에 재개

requestIdleCallback(workLoop);

}

}

**핵심**:

- `deadline.timeRemaining()`: 브라우저가 "쉴 시간" 알려줌.

- 시간 남으면 계속 작업.

- 시간 다 쓰면 중단.

- 다음 idle에 재개.

**React 18에서는** `requestIdleCallback` 대신 `MessageChannel` 기반 자체 scheduler.

**두 가지 Phase**:

**Render phase** (중단 가능):

- Fiber 트리 생성/업데이트.

- Virtual DOM diff.

- Effect 수집.

- **부작용 없음**. 언제든 버려도 OK.

**Commit phase** (중단 불가):

- 실제 DOM 수정.

- DOM effect 실행.

- **빠르게 완료**.

- 원자적.

**이 분리가 중요**:

- Render를 여러 번 시도 가능.

- 더 나은 업데이트가 오면 **이전 render 폐기**.

- Commit만 "진짜" 변경.

**이점 1: 부드러운 애니메이션**

큰 업데이트 중에도:

- 60fps 유지.

- 애니메이션 끊김 없음.

- 사용자 입력 즉시 반응.

**이점 2: 우선순위**

**높은 우선순위**:

- 사용자 입력 (키보드, 마우스).

- 애니메이션.

- 스크롤.

**낮은 우선순위**:

- 데이터 fetch 후 업데이트.

- 큰 리스트 렌더링.

- 백그라운드 작업.

React가 **자동으로 우선순위 처리**:

사용자 입력 발생

Scheduler: "high priority task"

현재 render 중단

입력 처리 먼저

입력 완료 후 render 재개

**이점 3: Concurrent features**

Fiber의 중단/재개 능력이 **concurrent mode**의 기반:

- **useTransition**: "이 업데이트는 중요하지 않음".

- **useDeferredValue**: "이 값은 지연되어도 OK".

- **Suspense**: "로딩 중엔 fallback".

이 모든 것이 **Fiber 없이는 불가능**.

**실전 예시**:

function App() {

const [query, setQuery] = useState('');

const [isPending, startTransition] = useTransition();

const [results, setResults] = useState([]);

function handleChange(e) {

setQuery(e.target.value); // high priority

startTransition(() => {

setResults(

expensiveSearch(e.target.value) // low priority

);

});

}

return (

<>

{isPending ? <Spinner /> : <Results items={results} />}

</>

);

}

**React 17 이전**:

- 각 키 입력이 **전체 업데이트** 트리거.

- 1 키 = query state + results state 둘 다 업데이트.

- expensiveSearch가 느리면 입력 버벅임.

**React 18 (Fiber + Concurrent)**:

- `setQuery`: 즉시 (input 반영).

- `startTransition` 안의 `setResults`: 낮은 우선순위.

- 사용자가 계속 타이핑 중이면 **이전 results 계산 폐기**, 새로 시작.

- Input은 **항상 부드럽게**.

**사용자 경험**: 이전엔 "Laggy", 지금은 "Smooth".

**이점 4: Double Buffering**

Fiber는 **두 개의 트리** 유지:

- **current**: 현재 화면.

- **workInProgress**: 작업 중.

작업이 완료되기 전까지 **current는 건드리지 않음**. 중간에 중단되어도 **화면은 그대로**.

완료되면 **atomic swap**. WorkInProgress → current.

**이점 5: Error 복구 (미래)**

Render 중 에러 발생 시:

- WorkInProgress 폐기.

- Current는 그대로.

- 복구 가능성.

**Error Boundary** 동작의 기반.

**단점과 복잡성**:

**1. 구현 복잡**:

- Linked list 기반 트리 순회.

- 우선순위 관리.

- Double buffering.

- **React 팀의 수년간 작업**.

**2. Memory 증가**:

- Alternate tree.

- Effect list.

- 약간의 overhead.

**3. Debugging 어려움**:

- 비선형 실행.

- DevTools가 이를 숨김.

**4. Legacy 호환성**:

- 일부 라이브러리가 깨짐.

- `useSyncExternalStore` 등으로 해결.

**역사적 관점**:

**2017년**: Fiber 출시 (React 16).

- 내부만 변경. 사용자 API 그대로.

- Concurrent features는 **opt-in** (React 18까지).

**2022년**: React 18.

- Automatic batching, transitions, Suspense 기본.

- 진짜 concurrent mode.

**2024년**: React 19.

- React Compiler.

- Server Components 성숙.

**Fiber는 5년 이상 걸린 긴 여정의 결과**. 사용자는 대부분 몰랐지만, React의 **근본 혁신**이었다.

**교훈**:

Fiber의 가치는 **"중단 가능성"** 이라는 단순한 개념이다. 하지만 이것이:

- 애니메이션 부드러움.

- 입력 반응성.

- 우선순위.

- Concurrent features.

- 자동 최적화.

**모두를 가능하게** 한다.

**"작업을 작게 쪼개서 중단 가능하게 만들라"** 는 원칙은 운영체제, 네트워킹, 분산 시스템 어디서나 유효하다. React Fiber는 **이 원칙을 UI rendering에 적용**한 성공 사례다.

당신이 React 앱을 쓸 때, 부드러운 스크롤, 반응하는 입력, 진행 중 transitioning의 뒤에는 **Fiber**가 있다. 알아차리지 못할 때 잘 작동하고 있는 것이다.

마치며: 추상화의 예술

핵심 정리

1. **Virtual DOM**: JavaScript 객체로 UI 표현.

2. **Reconciliation**: Diff + 효율적 업데이트.

3. **Fiber**: 중단 가능한 작업 단위.

4. **Scheduler**: 우선순위 기반 작업 관리.

5. **Concurrent Rendering**: Transitions, Suspense.

6. **Hooks**: Fiber에 저장된 state.

7. **React 19**: Compiler, Server Components.

React가 잘한 것

1. **Declarative**: UI = f(state).

2. **Component-based**: 재사용 가능.

3. **Virtual DOM**: 효율적 업데이트.

4. **Ecosystem**: 무한한 라이브러리.

5. **Evolution**: Class → Hooks → Concurrent → Compiler.

실전 조언

1. **기본을 신뢰하라**: 과도한 최적화 금지.

2. **Profiler 사용**: 실제 병목 찾기.

3. **State 위치**: 쓰이는 곳에 가깝게.

4. **Keys 제대로**: ID 사용.

5. **Hooks 규칙**: ESLint로 강제.

6. **React 19 기다려**: Compiler가 많은 걸 해결.

마지막 교훈

React는 **추상화의 힘**을 보여준다. 수십억 줄의 DOM 조작 코드를 **선언적 컴포넌트**로 대체했다.

그 아래에는 **엄청난 엔지니어링**:

- Fiber 아키텍처.

- 중단 가능한 렌더링.

- 우선순위 스케줄링.

- Hooks의 클로저 마법.

- 컴파일러 최적화.

**모든 것이 숨겨져 있다**. 개발자는 그저 JSX를 쓴다. 이것이 **좋은 추상화**다.

하지만 **내부를 아는** 개발자가 더 나은 코드를 쓴다. 왜 이 렌더가 발생하는지, 언제 memoize해야 하는지, 왜 hook 규칙이 있는지 — 이해하면 모든 것이 명확해진다.

당신이 다음에 `setState`를 호출할 때, 그 뒤에서 일어나는 일을 상상해 보라:

- State update 스케줄.

- Fiber tree 생성.

- Reconciliation.

- Diff.

- Effect 수집.

- Commit.

- DOM update.

- Effects 실행.

**한 줄의 코드**가 **이 모든 작업**을 trigger한다. 마법처럼 보이지만, 실제론 **우아한 엔지니어링**이다.

React의 교훈: **복잡성을 숨기되, 원할 때 들여다볼 수 있게 만들라**. 이것이 좋은 라이브러리의 정의다. React는 이 균형을 10년 넘게 지켜왔고, 앞으로도 계속 진화할 것이다.

참고 자료

- [React 공식 문서](https://react.dev/)

- [React Fiber Architecture (Andrew Clark)](https://github.com/acdlite/react-fiber-architecture)

- [React 내부: Fiber Reconciler](https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react)

- [Dan Abramov: Overreacted.io](https://overreacted.io/)

- [React Source Code](https://github.com/facebook/react)

- [React 18 Release Notes](https://react.dev/blog/2022/03/29/react-v18)

- [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components)

- [React Compiler Docs](https://react.dev/learn/react-compiler)

- [Concurrent Rendering Adoption Guide](https://react.dev/reference/react/Suspense)

- [Kent C. Dodds Blog](https://kentcdodds.com/blog)

현재 단락 (1/899)

2013년 Facebook이 공개한 React는 **프런트엔드의 표준**이 되었다:

작성 글자: 0원문 글자: 20,356작성 단락: 0/899