Introduce
framework에서의 lifecycle은 component가 DOM에 추가되어 브라우저에 의해 렌더링되는 시점(마운트)부터 DOM에서 제거되는 시점(언마운트)까지 여러 단계를 포함한다. 각 프레임워크마다 다른 lifecycle을 가지며 컴포넌트가 마운트될 때, 렌더링될 때, 언마운트될 때, 그리고 이들 사이의 여러 단계에서 특정 작업을 수행할 수 있다.
이 글에서는 React의 핵심 개념인 Virtual DOM, React Fiber, Reconciliation, Scheduling, Commit Phase, Render Phase 등에 대해 다룰 것이다. 이러한 개념을 이해하여 React 애플리케이션의 성능과 사용자 경험을 최적화하는 데 도움을 받을 수 있길 바란다.
Preliminaries
Virtual DOM
Virtual DOM(이하 VDOM)은 새로운 UI를 JavaScript 메모리에 유지하는 방법이다. diffing algorithm을 통해 업데이트된 Virtual DOM과 현재 렌더링된 DOM 간의 차이를 비교한 후 그 차이를 실제 DOM에 적용한다. 이 diffing algorithm을 Reconciliation이라고 한다. 이를 통해 React의 declarative API를 가능하게 한다. 즉, 개발자가 원하는 UI 상태를 React에 전달하면 React는 VDOM을 통해 실제 DOM이 그 상태를 반영하도록 한다.
VDOM은 일반적으로 UI를 나타내는 객체인 React element와 관련이 있다. 또한 React는 fiber라는 내부 객체도 사용하여 컴포넌트 트리에 대한 추가 정보를 가지고 있다.
Fiber
Fiber는 React v16에서 추가된 개념으로 work의 단위이며 새로운 reconciliation engine이다. Fiber는 scheduling을 더 원활하게 하기 위해서 추가된 engine이다. Javascript는 싱글 스레드 방식으로 Call stack을 사용하여 작업들을 처리한다. 그러나 웹 페이지를 구성할 때 애니메이션을 렌더링 하는 작업 등은 끊기지 않기 위해 우선 순위가 다른 작업들보다 높을 수 있다. 이를 위해 work를 멈추고 다시 돌아와서 작업하고, work에 우선순위를 부여하고 완료된 work를 재사용하는 기능 등이 필요하다.
Fiber를 통해 이를 가능케 하며 Fiber는 React component를 위해 stack을 virtual하게 구성한 것이다. 이를 통해 stack frame을 메모리에 유지하고, 원하는 방식과 시점에 실행할 수 있다. 따라서 signle fiber는 virtual stack frame으로 생각할 수도 있다.
Fiber를 통해 React 애플리케이션의 성능과 반응성을 크게 향상시킬 수 있고 각 work를 세밀하게 관리할 수 있다.
React fiber에 관한 더 자세한 글과 영상은 여기 있다.
Update
데이터에 변화가 생겨 React App을 변화시키는 과정이다. 주로 setState에 의해 일어나며, re-render를 야기한다.
Scheduling
work가 언제 수행되어야 하는지를 결정하는 과정을 의미한다.
Work
수행해야 하는 모든 계산을 의미한다. work는 보통 update(e.g. setState)의 결과로 발생한다.
React Lifecycle
React의 라이프사이클은 먼저 컴포넌트가 렌더링될 필요가 있는 상황에서 시작된다. 렌더가 트리거되는 이유는 두 가지다.
초기 렌더링
애플리케이션이 처음 시작될 때, React는 처음으로 컴포넌트를 렌더링해야 한다. 초기 렌더는 주로 ReactDOM.createRoot()와 root.render()로 트리거된다. 이때 React는 DOM의 특정 요소를 타겟으로 첫 번째 렌더링을 시작한다.
상태 업데이트로 리렌더링
초기 렌더링 이후, 컴포넌트는 상태가 변경될 때마다 다시 렌더링된다. useState와 같은 훅을 통해 상태를 업데이트하면, React는 자동으로 해당 상태의 변화를 감지하고 리렌더를 트리거한다. 이때 중요한 점은 UI 트리의 구조에 따라 상태가 유지되거나 파괴될 수 있다는 것이다.
만약 컴포넌트가 다른 컴포넌트로 교체되거나, 위치가 변경되면 React는 상태를 파괴하고 새로 생성된 컴포넌트에 대해 다시 렌더링을 수행한다.
Render Phase
render phase는 렌더링 프로세스의 초기 단계로, 이 단계에서 JSX 코드는 JavaScript로 변환된다. 예를 들어 <h1>Hello, world!</h1>는 React.createElement('h1', null, 'Hello, world!')로 변환된다. 초기 렌더링 시에는 루트 컴포넌트에서 시작하여 하위 컴포넌트로 내려가면서 실제 DOM이 어떻게 생겨야 하는지를 나타내는 React element 트리를 구축한다. 이 과정에서 React는 각 컴포넌트를 렌더링하고, 그 결과로 가상 DOM을 생성한다.
재렌더링 시에도 유사한 접근 방식을 따르지만, 중요한 차이점이 있다. 재렌더링 시에는 업데이트가 필요한 모든 컴포넌트를 식별한다. 그런 다음, React는 diffing이라는 과정을 통해 실제 DOM과 새로운 가상 DOM간의 변경 사항을 식별한다. 이 과정에서 변경된 부분만 실제 DOM에 반영되므로, 전체 페이지를 다시 렌더링하지 않고도 효율적으로 업데이트할 수 있다.
render phase는 컴포넌트의 state나 props가 변경될 때마다 일어나며 이 단계에서 변경된 사항을 반영하기 위해 새로운 가상 DOM이 생성된다.
Reconciliation
Reconciliation은 현재의 React element 트리와 새롭게 생성된 트리를 비교하여, 어떤 부분이 변경되어야 하는지를 결정하는 diffing algorithm이다. 이는 전체 애플리케이션을 다시 렌더링하지 않고도 새로운 트리를 다시 렌더링하는 것처럼 보이게 한다.
React의 diffing 알고리즘은 두 가지 주요 가정을 기반으로 하는 heuristic O(n) 알고리즘을 사용한다. 첫 번째 가정은 서로 다른 타입의 두 요소는 완전히 다른 트리를 생성한다는 것이다. 이는 root 요소의 타입이 다르면 전체 서브트리가 교체되어야 한다는 것을 의미한다. 두 번째 가정은 개발자가 key prop을 사용하여 어떤 child element가 서로 다른 render에서 stable한지 React에게 힌트를 줄 수 있다는 것이다. 이러한 가정을 통해 React는 효율적으로 트리를 비교하고 업데이트할 수 있다.
Reconciliation 과정은 다음과 같은 단계로 이루어진다. 먼저, React는 현재 트리와 새로운 트리의 root 요소를 비교한다. root 요소의 타입에 따라 그 후의 행동이 달라진다. 만약 root 요소의 타입이 같다면 React는 재귀적으로 자식 요소들을 비교한다. children element의 key prop은 이 과정에서 중요한 역할을 하며, 원래의 tree와 새로운 tree에서 element들을 더 효율적으로 매칭하는 데 도움이 된다.
이후, Reconciliation은 트리의 변경된 부분을 계산하고 render는 이 정보를 사용하여 실제로 DOM을 업데이트한다. 이렇게 함으로써 UI는 최신 상태를 반영하게 된다.
Commit phase
렌더링이 완료되면, React는 DOM에 실제 변경 사항을 적용하는 커밋 단계로 넘어간다.
초기 커밋
최초의 렌더링에서는 React가 모든 DOM 노드를 생성하고, appendChild()와 같은 API를 사용해 화면에 추가한다. React는 가상 DOM을 기반으로 실제 DOM을 구축하고, 이를 화면에 반영하기 위해 필요한 모든 노드를 한 번에 추가한다. 이 단계에서 사용자는 처음으로 애플리케이션 UI를 화면에서 볼 수 있게 된다.
재렌더링 커밋
상태가 업데이트되어 컴포넌트가 재렌더링될 경우, React는 이전 렌더링 결과와 현재 렌더링 결과를 비교해 변경된 부분만 DOM에 적용한다. 이를 minimal necessary operations이라고 한다. React는 변경된 속성이나 노드만 업데이트하여 불필요한 리소스 소모를 줄인다.
중요한 점은 React가 실제 DOM과 직접적으로 소통하지 않는다는 것이다. 대신, React는 Renderers라고 불리는 서드파티 패키지를 사용하여 실제 DOM 조작을 처리한다. 웹을 위한 React DOM과 모바일을 위한 React Native가 이러한 Renderers의 예이다. React는 컴포넌트 정의와 같은 표현 수단을 제공하며, 실제 DOM 조작은 이러한 Renderers를 통해 이루어진다.
React 웹 애플리케이션에서는 보통 index.js 파일에서 React DOM 패키지를 한 번만 import하고, 그 다음 render 메서드를 호출하여 애플리케이션을 초기화한다. React는 hook같은 lifecycle method를 사용하여 Renderer와 소통하며, 이를 통해 실제 DOM 조작을 수행한다.
commit phase는 이처럼 실제 DOM에 변경 사항을 적용하는 단계이다. 이를 통해 React 애플리케이션은 사용자 인터페이스를 효율적으로 업데이트하고, 최적화된 성능을 유지할 수 있다.
예를 들어, props가 변경된 컴포넌트는 화면에 새로운 값을 렌더링하지만, 입력 필드의 텍스트와 같은 사용자가 입력한 데이터는 그대로 유지될 수 있다. 이 방식 덕분에 성능이 최적화된다.
References
- Mozilla Developer Network. (2024, July 10). Writing components. Retrieved from https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features#writing_components
- Clark, A. (2024, July 10). React Fiber architecture. GitHub. Retrieved from https://github.com/acdlite/react-fiber-architecture?tab=readme-ov-file
- React. (2024, July 10). What is the virtual DOM? Retrieved from https://legacy.reactjs.org/docs/faq-internals.html#what-is-the-virtual-dom
- Maj, W. (2024, July 10). React lifecycle methods diagram. Retrieved from https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
- React. (2024, July 10). Reconciliation. Retrieved from https://legacy.reactjs.org/docs/reconciliation.html
- React. (2024, July 10). What is the virtual DOM? Retrieved from https://legacy.reactjs.org/docs/faq-internals.html#what-is-the-virtual-dom
- Zlatev, A. (2024, July 10). Understand how rendering works in React. Telerik Blogs. Retrieved from https://www.telerik.com/blogs/understand-how-rendering-works-react
'React' 카테고리의 다른 글
React 컴포넌트 이해하기: UI의 기본 단위 (0) | 2024.09.02 |
---|---|
React로 UI 구성: 단계별 접근 가이드 (0) | 2024.08.23 |
SPA와 MPA의 차이, 작동 원리에 대해서 (feat CSR, SSR) (0) | 2024.07.04 |
React와 JWT로 자동 로그인 구현하기 (0) | 2023.12.24 |
동적 라우팅을 이용한 게시글 별로 달라지는 URL 구현 (0) | 2023.08.06 |