Skip to content

Latest commit

 

History

History
268 lines (182 loc) · 17.8 KB

File metadata and controls

268 lines (182 loc) · 17.8 KB

1. React의 기본 렌더링 과정

React에서의 렌더링 과정은 크게 세 단계로 나눌 수 있음:

  1. 렌더 단계 (Render Phase): 컴포넌트 트리를 탐색하고, 새로운 UI를 생성하기 위한 가상 DOM을 만드는 단계
  2. 조정 (Reconciliation Phase): 이전의 가상 DOM과 새로운 가상 DOM을 비교하여 변경 사항을 찾아내는 단계
  3. 커밋 단계 (Commit Phase): 변경 사항을 실제 DOM에 반영하는 단계

이 모든 과정은 Fiber 아키텍처를 통해 관리되며, Fiber는 React가 비동기적이고 효율적인 렌더링을 구현하기 위한 핵심 메커니즘임

렌더링과 Commit

  1. 렌더 단계: 이 단계에서는 컴포넌트가 업데이트되거나 새로운 컴포넌트가 렌더링될 때, React는 해당 컴포넌트의 새로운 가상 DOM 트리를 생성함. 이 트리는 실제 DOM에 반영되기 전의 초안이라고 생각하면 됨.
  2. Commit 단계: 새로운 가상 DOM 트리가 생성된 후, 그 변화가 실제 DOM에 적용되는 단계임. 이때 React는 실제로 DOM을 업데이트하고 화면에 변화를 적용함.

2. 함수형 컴포넌트와 클래스형 컴포넌트의 차이

클래스형 컴포넌트:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  componentDidMount() {
    // 마운트 후 호출
  }

  render() {
    return <div>{this.state.count}</div>;
  }
}

동작 원리:

  1. 클래스 인스턴스 생성: 클래스형 컴포넌트는 클래스의 인스턴스가 생성되며, 각 인스턴스는 stateprops를 포함하는 고유의 객체를 가짐.
  2. 생명주기 메서드: 클래스형 컴포넌트는 componentDidMount, componentDidUpdate, componentWillUnmount생명주기 메서드를 통해 컴포넌트의 상태와 DOM이 어떻게 업데이트되는지를 제어할 수 있음.
  3. this: 클래스형 컴포넌트에서는 this가 인스턴스를 참조하므로, this.statethis.props를 사용하여 상태와 props를 다룸.

주요 특징:

  • State 관리: this.state로 상태를 관리하고, setState로 상태를 업데이트함.
  • 생명주기 메서드: 컴포넌트의 각 단계(마운트, 업데이트, 언마운트)에서 특정 동작을 정의할 수 있음.
  • 클래스 인스턴스의 메모리 비용이 더 큼.
    • 인스턴스 객체 생성으로 인한 메모리 사용 증가: 각 컴포넌트마다 인스턴스가 생성되어 메모리를 차지함.
    • 메서드 바인딩으로 인한 중복 메서드 생성: 이벤트 핸들러를 사용하기 위해 메서드를 바인딩하면 메모리 사용이 늘어남.
    • 프로토타입 체인과 상속 구조로 인한 오버헤드: 상속과 프로토타입 체인으로 인해 추가적인 메모리와 시간이 소요됨.
    • this 컨텍스트 관리로 인한 추가 메모리 사용: this를 유지하고 관리하기 위한 메모리 사용이 발생함.

함수형 컴포넌트:

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

  useEffect(() => {
    // 마운트 후 동작
  }, []);

  return <div>{count}</div>;
}
  • jsx는 트랜스파일러를 통해 일반적인 javascript 코드로 변환
// JSX 코드
const element = <h1>Hello, world!</h1>;

// 변환된 JavaScript 코드
const element = React.createElement("h1", null, "Hello, world!");
  • createElement 함수를 통해 React 엘리먼트를 생성하는 함수 호출로 변환
  • 함수형 컴포넌트를 통해 생성된 React 엘리먼트들은 트리 구조로 결합되어 가상 DOM을 형성
  • 동작 overview
    • JSX 작성: 개발자는 함수형 컴포넌트 내에서 JSX를 반환
    • 트랜스파일: 빌드 도구를 통해 JSX가 React.createElement 호출로 변환
    • React 엘리먼트 생성: React.createElement 함수 호출을 통해 React 엘리먼트 객체가 생성
    • 가상 DOM 트리 구성: 이러한 React 엘리먼트들이 모여 가상 DOM 트리를 형성
    • 렌더링 및 조정: React는 가상 DOM 트리를 기반으로 변경 사항을 파악하고, 실제 DOM 업데이트가 필요한 부분을 결정
    • 실제 DOM 업데이트(Commit 단계): 필요한 경우 실제 DOM에 변경 사항을 반영하여 화면을 업데이트

동작 원리:

  1. 간단한 함수 호출: 함수형 컴포넌트는 단순히 함수를 호출하는 방식으로 동작함. 상태는 React Hook(예: useState, useEffect)을 사용하여 관리됨.
  2. 생명주기 관리: 클래스형 컴포넌트의 생명주기 메서드와 달리, 함수형 컴포넌트는 Hooks를 사용하여 생명주기 동작을 대체함.
    • useEffectcomponentDidMount, componentDidUpdate, componentWillUnmount를 통합한 형태로 사용됨.

주요 특징:

  • Hooks: useState, useEffect 같은 Hook을 통해 상태 및 생명주기 관련 로직을 처리함.
  • 간결성: 함수형 컴포넌트는 코드가 더 간결하고 구조가 명확함.
  • 메모리 비용이 상대적으로 적음(클래스 인스턴스를 생성하지 않음).

3. Fiber 아키텍처

Fiber가 도입된 이유:

  • React 16에서 도입된 Fiber는 React의 코어 재구성을 통해 렌더링 작업을 더 세밀하게 제어하고, 비동기적 업데이트를 가능하게 하여 보다 효율적인 렌더링을 제공하기 위해 만들어졌음.
  • Fiber의 핵심 목표는 시간 분할 렌더링(time slicing)이라고 불리는 방식으로, 큰 렌더링 작업을 작은 작업 단위로 나누어 브라우저의 이벤트 처리 및 애니메이션 성능을 저하시키지 않도록 하는 것임.

Fiber의 동작 원리:

  • Fiber는 가벼운 작업 단위로, 각 작업을 완료하기 전에 브라우저가 해야 할 중요한 작업(예: 사용자 입력, 애니메이션)을 처리할 수 있도록 작업을 일시 중단했다가 다시 재개할 수 있음.
  • React는 이 Fiber를 사용해 렌더링 우선순위를 정하고, 우선순위가 높은 작업을 먼저 처리할 수 있게 함.

Fiber의 구현체:

1. Fiber 노드 구조

  • Fiber 노드는 컴포넌트의 정보를 담고 있는 자바스크립트 객체임.
  • 각 Fiber 노드는 다음과 같은 정보를 가짐:
    • 타입(type): 어떤 컴포넌트인지 나타냄 (함수형, 클래스형, DOM 노드 등).
    • 키(key): 리스트에서 요소를 고유하게 식별하기 위한 값.
    • 상태와 props: 해당 컴포넌트의 상태와 속성 정보.
    • 자식, 형제, 부모 노드에 대한 참조: 트리 구조를 구성하기 위해 다른 노드들과 연결됨.

2. 링크드 리스트 형태의 트리 구성

  • Fiber 노드들은 단일 연결 리스트 형태로 트리를 구성함.
  • child, sibling, return(부모) 포인터를 통해 노드들이 연결되어 있음.
  • 이러한 구조를 통해 React는 트리를 순회하면서 필요한 작업을 수행함.

3. 작업의 분할과 스케줄링

  • Fiber 아키텍처는 작업을 작은 단위로 분할하여 스케줄러가 관리할 수 있게 함.
  • 각 작업 단위는 우선순위를 가지며, React는 이 우선순위에 따라 작업을 수행함.
  • 스케줄러는 브라우저의 빈 시간에 맞춰 작업을 수행하고, 긴 작업으로 인해 UI가 멈추는 것을 방지함.
  • 높은 우선순위의 작업은 즉시 처리하고, 낮은 우선순위의 작업은 브라우저가 한가할 때 처리
  • React의 스케줄러는 브라우저와 협력하여 작업을 관리, 이는 작업이 브라우저의 이벤트 루프와 조화롭게 동작하도록 도와줌
  • 작업 양보(Yielding): 작업을 수행하다가 일정 시간이 지나면, React는 작업을 중단하고 브라우저에게 제어권을 넘김. 이를 통해 브라우저는 사용자 입력 처리나 렌더링 등의 중요한 작업을 수행할 수 있음
  • 브라우저의 빈 시간 활용: Idle Callback 사용 - 브라우저가 유휴 상태일 때 실행되는 requestIdleCallback API를 활용하여 낮은 우선순위의 작업을 수행. 하지만 이 API는 모든 브라우저에서 지원되지 않으므로, React는 타이머와 메시지 채널 등을 사용하여 유사한 기능을 구현
  • 타임 슬라이스(Time Slice): 각 작업은 일정 시간(예: 5ms) 내에서만 실행되고, 시간이 초과되면 작업을 중단하고 다음 프레임에서 이어서 실행.
  • 긴 작업으로 인한 UI 멈춤 방지: 메인 스레드 블로킹 방지: 긴 JavaScript 실행으로 인해 메인 스레드가 차단되면, 사용자 인터페이스가 응답하지 않게 된다. Fiber 아키텍처는 이를 방지하기 위해 작업을 분할하고 스케줄링하여 메인 스레드의 부담을 줄임

4. Stack 기반 재귀에서 반복문으로 전환

  • 기존 React는 재귀적으로 컴포넌트 트리를 탐색했지만, Fiber는 이를 반복문으로 전환함.
  • 이를 통해 호출 스택의 제한 없이 큰 규모의 트리를 효율적으로 탐색할 수 있음.

5. 작업 중단과 재개

  • Fiber는 작업 중간에 중단하고 나중에 재개할 수 있음.
  • 이를 통해 React는 긴 렌더링 작업을 수행하면서도 사용자 인터랙션에 빠르게 반응할 수 있음.

Fiber 트리와 렌더링 과정:

  1. 현재 트리(Current Tree): 현재 화면에 렌더링된 트리임.
  2. 작업 중 트리(Work-in-progress Tree): 새로운 렌더링 작업이 수행되는 트리임.
    • 렌더링 단계에서 React는 이 작업 중 트리를 구성하면서 새롭게 업데이트된 컴포넌트들을 처리함.
    • 이 과정에서 각 컴포넌트는 Fiber 노드로 변환되고, 필요한 작업이 작은 단위로 나누어 처리됨.
    • 렌더링이 완료되면 작업 중 트리는 현재 트리로 대체되어 커밋 단계로 넘어감.

추가 설명:

  • 우선순위 처리: Fiber는 각 작업에 우선순위를 부여하여 긴 렌더링 작업이 사용자 경험을 해치지 않도록 함. 예를 들어, 사용자의 입력이나 애니메이션 같은 고우선순위 작업은 즉시 처리되고, 나머지 렌더링 작업은 나중에 처리됨.
  • Interruptible Rendering: Fiber 아키텍처 덕분에 React는 렌더링 작업을 중단하고 필요한 경우 나중에 재개할 수 있음. 이는 브라우저의 메인 스레드에서 긴 작업으로 인해 UI가 응답하지 않는 문제를 해결함.
  • 협력적 스케줄링: React는 작업을 작은 단위로 나누어 실행하고, 각 단위 작업이 끝날 때마다 브라우저에게 제어권을 넘겨줌. 이를 통해 중요한 브라우저 작업(예: 사용자 이벤트 처리)이 지연되지 않도록 함.

4. Reconciliation (조정 과정)

React는 Reconciliation을 통해, 기존의 가상 DOM과 새로운 가상 DOM을 비교하고 최소한의 업데이트만 실제 DOM에 적용함. 이 과정을 통해 불필요한 DOM 업데이트를 줄이고 성능을 향상시킴.

동작 원리:

  1. 변경 사항 감지: 새로운 상태나 props가 전달되면, React는 기존 가상 DOM과 새로운 가상 DOM을 비교함.
  2. Diffing 알고리즘: React는 변경 사항을 빠르게 찾아내기 위해 Diffing 알고리즘을 사용함. 이 알고리즘은 다음 원칙에 따라 동작함:
    • 같은 타입의 요소는 변경 사항만 업데이트함.
    • 다른 타입의 요소는 기존 요소를 제거하고 새로 렌더링함.
    • Key를 사용하여 리스트 항목의 순서를 추적함.

추가 설명:

  • 효율성: Reconciliation 과정에서 React는 가능한 한 최소한의 조작으로 실제 DOM을 업데이트하려고 함. 이는 DOM 조작이 비용이 많이 들기 때문임.
  • Key의 중요성: 리스트를 렌더링할 때 key 속성을 제공하면 React가 각 항목을 고유하게 식별하여 변경 사항을 더 정확하게 추적할 수 있음.

5. Commit 단계

Commit 단계에서는 Reconciliation을 통해 찾아낸 변경 사항을 실제 DOM에 적용함. 이 단계에서는 세 가지 작업이 이루어짐:

  1. DOM 업데이트: 변경된 노드를 실제 DOM에 반영함.
  2. ref 콜백 호출: ref 콜백을 통해 참조된 DOM 노드를 업데이트함.
  3. 생명주기 메서드 호출: componentDidMount, componentDidUpdate 같은 생명주기 메서드를 호출함.

추가 설명:

  • 동기 실행: Commit 단계는 렌더 단계와 달리 동기적으로 실행됨. 이는 DOM 업데이트가 사용자에게 즉각적으로 반영되어야 하기 때문임.
  • 오류 처리: 이 단계에서 발생하는 오류는 React의 오류 경계(Error Boundary)에 의해 처리될 수 있음.

6. 함수형 컴포넌트의 추가 주제

Hooks의 규칙

  • 최상위에서만 호출: Hooks는 컴포넌트의 최상위 수준에서만 호출해야 함. 루프나 조건문, 중첩된 함수 내에서 호출하면 안 됨.
  • React 함수 내에서만 호출: Hooks는 React 함수 컴포넌트나 커스텀 Hook 내에서만 호출해야 함.

커스텀 Hooks

  • 재사용성: 커스텀 Hook을 만들어 로직을 재사용할 수 있음.
  • 명명 규칙: 커스텀 Hook의 이름은 use로 시작해야 함.

성능 최적화

  • useMemouseCallback: 함수나 값을 메모이제이션하여 불필요한 재렌더링을 방지할 수 있음.
  • React.memo: 컴포넌트를 메모이제이션하여 props가 변경되지 않으면 재렌더링을 방지함.
  • 주의점: 메모이제이션은 메모리를 사용하므로, 불필요한 경우 사용을 피하는 것이 좋음.

7. 클래스형 컴포넌트의 추가 주제

PureComponent

  • 불필요한 업데이트 방지: React.PureComponent를 상속하면 shouldComponentUpdate를 구현하지 않아도, 얕은 비교를 통해 불필요한 업데이트를 방지함.

에러 경계(Error Boundaries)

  • 오류 처리: 클래스형 컴포넌트에서만 에러 경계를 구현할 수 있음. componentDidCatchgetDerivedStateFromError를 사용하여 하위 컴포넌트에서 발생하는 오류를 잡을 수 있음.

8. React의 효율적인 렌더링을 위한 추가 고려사항

불변성 유지

  • 상태 업데이트: 상태를 업데이트할 때 불변성을 유지하여 React가 변경 사항을 정확히 감지할 수 있도록 해야 함.
  • Immutable 데이터 구조: Immutable.js 같은 라이브러리를 사용하면 불변성을 유지하는 데 도움이 됨.

배치 업데이트

  • 성능 향상: React는 여러 상태 업데이트를 하나의 렌더링으로 배치하여 성능을 향상시킴.
  • unstable_batchedUpdates: 비동기 코드에서 배치 업데이트를 수동으로 제어할 수 있음.

가상화

  • 리스트 렌더링 최적화: 많은 양의 리스트를 렌더링할 때 가상화 기법을 사용하여 성능을 개선할 수 있음.
  • React Window 및 React Virtualized: 이러한 작업을 돕는 라이브러리들이 있음.

코드 스플리팅

  • 번들 크기 감소: 코드 스플리팅을 통해 초기 로딩 시간을 줄일 수 있음.
  • React.lazySuspense: 동적 임포트를 활용하여 컴포넌트를 필요할 때만 로드함.

요약

  • React의 함수형 컴포넌트는 간결하며, Hooks를 통해 상태와 생명주기 관리를 수행함.
  • 클래스형 컴포넌트는 생명주기 메서드와 this를 사용하여 상태와 동작을 관리하지만 더 복잡할 수 있음.
  • Fiber 아키텍처비동기적 렌더링을 가능하게 하여 React가 효율적으로 렌더링을 제어할 수 있게 함.
  • Reconciliation은 가상 DOM을 통해 최소한의 DOM 업데이트를 적용하는 과정이며, Commit 단계에서 변경 사항이 실제 DOM에 반영됨.
  • 추가적으로, React의 성능과 효율성을 높이기 위해 다양한 기법과 원칙을 이해하고 적용해야 함.

전반적으로 더 쉽게 설명을 추가하자면:

  • Fiber 아키텍처는 React가 큰 작업을 작은 조각으로 나누어 처리할 수 있게 해주는 새로운 엔진이라고 볼 수 있음. 이를 통해 React는 사용자 인터페이스를 더 부드럽고 빠르게 업데이트할 수 있음.
    • 예를 들어, 큰 리스트를 렌더링할 때 이전에는 한 번에 모든 작업을 처리해야 했지만, 이제는 부분적으로 작업을 나누어 사용자에게 더 빠른 응답성을 제공할 수 있음.
  • 함수형 컴포넌트는 더 간단하고 코드량이 적어 개발자가 이해하고 유지보수하기 쉬움.
    • Hooks를 사용하면 상태 관리와 생명주기 메서드를 함수 내에서 간단하게 처리할 수 있음.
  • 클래스형 컴포넌트는 더 많은 코드와 복잡성을 가지지만, 기존의 생명주기 메서드를 사용하여 상태와 동작을 관리함.
    • this 키워드를 사용해야 하고, 메서드 바인딩 등 추가 작업이 필요함.

결론적으로, React의 내부 작동 방식을 이해하면 디버깅이 쉬워질 뿐만 아니라 더 효율적이고 성능 좋은 애플리케이션을 개발할 수 있음. 특히 Fiber 아키텍처의 구현 방식과 그로 인한 이점을 알면 React의 강력한 기능을 최대한 활용할 수 있겠다!

Reference