04

4주차 스터디

10~12장 · 3개 챕터

10. 리액트의 프롭스와 컴포넌트 패턴 돌아보기

리액트의 props

  • 트리 구조 + 단방향 데이터 흐름
  • 리액트는 상태 업데이트 시 불변성 전재 -> 이전 값과 새로운 값의 참조 만을 비교하는 얕은 비교(Shallow Comparison)를 사용해 상태 변경 확인 (Object.is)

프롭스를 사용하는 컴포넌트 패턴

  • children props 활용
    • 컴포넌트 내부에 고정된 UI를 두는 대신, children을 통해 외부에서 콘텐츠를 주입
  • 슬롯 Props 패턴 (Named Slots)
    • 단일 children 대신 여러 개의 props를 통해 특정 위치에 콘텐츠를 전달하는 방식
    • 예를 들어 header, footer, actions 같은 명시적인 슬롯을 제공하면, 컴포넌트의 구조를 유지하면서도 각 영역 커스터마이징 가능
  • 컴파운드 컴포넌트 패턴 (Compound Component)
    • 하나의 부모 컴포넌트와 그에 종속된 하위 컴포넌트를 조합해 사용하는 패턴
    • 내부 상태를 공유하면서도 선언적으로 조합 가능

11. 리액트의 상태와 배칭 돌아보기

리액트의 상태 정의와 종류

  1. 지역상태/파생상태
  • 지역상태: 컴포넌트 내부에서 직접 관리/외부에서 참조 불가능한 상태
  • 파생상태: 기존 상태나 props로부터 계산된 값
  1. 상태는 불변해야 한다.
  • 리액트는 Object.is() 기반의 비교(참조 비교)를 통해 상태 변경을 감지하므로, 상태가 바뀔 때는 항상 새로운 값(객체) 을 생성해야 한다
    • 참조값이 바뀌면(= Object.is가 다르면) 모든 Consumer가 무조건 다시 읽게해, 일관성 있는 최신 값을 보장한다.
  • 직접 수정하면 변경을 감지하지 못해 리렌더링이 일어나지 않을 수 있다.
  1. 상태 끌어올리기
  • 여러 컴포넌트가 동일한 상태를 공유해야 할 때, 가장 가까운 공통 부모에서 상태를 관리하고 props로 내려주는 패턴
  • 특징: 상태 일관성과 단방향 데이터 흐름을 유지

컴포넌트 간 데이터 흐름

  • 리액트는 단방향 데이터 흐름(부모 → 자식) 구조
  • 특징: 데이터의 출처를 명확하게 하고, 예측 가능한 구조를 만듦. 관심사 분리를 통해 불필요한 렌더링을 줄이고 유지보수를 쉽게 함

리액트 배칭 돌아보기

  • 정의: 여러 상태 업데이트를 하나로 묶어 한 번만 렌더링하는 최적화 메커니즘이다.
  • 특징: 여러 setState() 호출 → 한 번의 리렌더링 -> 성능 최적화 및 불필요한 렌더 방지
  • React 17 이전에는 이벤트 핸들러 내부에서만 자동 배칭이 적용되었고, 비동기 작업에서는 unstable_batchedUpdates가 필요했음
  • React 18부터는 대부분의 상황(Promise, setTimeout 등)에서 자동 배칭이 적용

flushSync()

flushSync(() => {
  setState(newState)
})
  • 동기적으로 상태를 즉시 반영해야 하는 경우 사용한다.
  • 일반적인 상황에서는 필요하지 않으며, DOM 측정 등 특수한 경우에만 사용한다.

12. 리액트를 구성하는 뿌리, 파이버 돌아보기

리액트 스택 재조정자 알아보기 (React v15 이전)

  • Fiber 아키텍처는 React에서 Reconciliation 성능 최적화를 위해 도입된 구조입니다.
  • React v15 이전에는 Stack 기반으로 렌더링 작업을 처리했습니다.
    • 특징: 호출 스택 기반의 재귀 순회 -> 호출 스택에서 작업이 하나씩 처리됩니다.
    • 작업이 중단될 수 없으며, 한 번 시작된 작업은 끝날 때까지 실행됩니다.
  • 문제점
    • 브라우저의 메인 스레드가 점유되기 때문에 렌더링 작업이 오래 걸리면 UI가 멈추는 UI 렉 문제가 발생
    • 사용자 입력 같은 긴급 작업이 대기 상태로 밀림
    • 작업을 나눠 처리하거나 우선순위를 조정할 수 없음

파이버 아키텍처 알아보기 (React v16+)

  • Fiber 아키텍처는 작업을 Fiber Node라는 작은 단위로 나누고 우선순위를 부여해, 중요한 작업(사용자 입력, 애니메이션 등)이 먼저 처리될 수 있도록 했습니다.
    • 호출 스택 대신 작업 단위(Fiber 객체) 기반 처리
    • 렌더링을 작은 단위로 쪼개서 실행
    • 작업의 중단, 재개, 우선순위 조정 가능

파이버 우선순위와 레인(Lanes) 모델

  • 파이버 아키텍처에서는 모든 업데이트가 동일하게 처리되지 않음. 작업마다 레인(Lane) 이라는 개념을 부여해 우선순위를 스케줄링
  • 레인: 작업의 긴급도를 표현하는 단위
    • 사용자 입력과 같은 긴급한 작업은 높은 우선순위 레인
    • 데이터 패칭과 같은 덜 긴급한 작업은 낮은 우선순위 레인에 배정
  • 이점: 메인 스레드를 독점하지 않으면서도, 사용자 경험을 해치지 않는 방식으로 렌더링을 수행 가능

파이버 트리 구조 : 재귀없는 순회

  • 포인터 기반 트리 구조를 사용
  • 각 Fiber 노드는 다음과 같은 연결 포인터를 가짐
    • child : 자식 노드
    • sibling : 형제 노드
    • return : 부모 노드
  • 재귀 호출 없이 while 루프 기반으로 객체 포인터를 따라 이동하며 트리를 순회

더블 버퍼링과 얼터네이트(Alternate) 포인터

  • "더블 버퍼링" 전략
    • 사용 이유: 렌더링 도중 화면에 보이는 UI를 직접 수정하면, 사용자는 완성되지 않은 “깨진 UI”를 보게 되는데, 이를 방지하기 위함
    • 동작 원리: 두 개의 파이버 트리 유지하고, 렌더링이 완료되면 WIP 트리를 Current 트리로 교체
      • Current 트리 : 현재 화면에 반영되어 사용자에게 보이는 UI 트리
      • Work-In-Progress(WIP) 트리 : 상태 변경 이후, 화면에 보이지 않는 곳에서 새롭게 렌더링을 수행하는 트리
    • 이점: 사용자에게는 항상 완성된 UI만 표시됨
  • 얼터네이트(Alternate) 포인터
    • 각 Fiber 노드는 alternate 포인터를 통해 Current 노드와 그에 대응하는 WIP 노드를 서로 참조
    • 이점: 기존 노드를 재사용할 수 있고 매 렌더마다 전체 트리를 새로 생성하지 않아도 되며 메모리 효율성과 성능을 함께 확보할 수 있음