React의 랜더링 개념 알아보기😎

연정·2023년 4월 7일
0

React

목록 보기
7/9
post-thumbnail

한 줄 한 줄 코드 작성에 급급했던 시간들을 지나, 요즘 들어 이제는 조금 더 효율적인 코드를 작성하고 싶다는 생각이 들기 시작했다.
효율적이면서도 성능이 좋은 코드를 작성하는 방법은 여러가지가 있겠지만, 나는 그 기본에 랜더링에 대한 이해가 있다고 생각한다.
그래서 리액트에서 랜더링이 무엇이고 어떤 방식으로 이루어지는지를 살펴보고자 한다.


그래서, 랜더링이 뭔데?

일반적으로 우리가 말하는 랜더링은 UI를 화면에 나타내는 과정을 의미한다.
하지만 리액트에서는 이를 조금 더 세분화하여 3가지의 단계로 설명하고 있다.
공식 문서에 랜더링과 요리를 비유한 설명이 있어 함께 가지고 왔다.

[ UI 요청과 전달의 3단계 ]
1. Triggering a render / 주문이요-
2. Rendering the component / 뚱땅뚱땅 요리중
3. Committing to the DOM / 주문하신 음식 나왔습니다

1단계 | Triggering a render

리액트 컴포넌트는 처음으로 app이 시작될 때컴포넌트 state에 업데이트가 있을 때 랜더링을 야기한다.
ex_ 손님이 처음 가게에 들어왔을 때와 추가로 먹고싶은게 생겼을 때 주문을 하는 것처럼 :)

2단계 | Rendering the component

rendering리액트가 컴포넌트를 호출하는 것을 의미한다.

최초 랜더링 (Initial Render)
root 컴포넌트를 호출한다.

리랜더링 (Re-render)
state가 업데이트된 function component를 호출한다.

3단계 | Committing to the DOM

컴포넌트를 호출하고 변경사항 확인이 완료되면, 리액트는 변경사항을 DOM에 적용한다.

최초 랜더링 (Initial Render)
appendChild() DOM API를 활용하여 모든 DOM 노드를 주입한다.

리랜더링 (Re-render)
최소한의 실행으로 DOM을 업데이트 한다.



최초 랜더링을 할 때는 어떤 일이 발생할까?

state 변경으로 인한 랜더링과 랜더링 내부 로직을 더 살펴보기 전에, 최초 랜더링 시에 발생하는 일을 우선 알아보자.

최초 랜더링 (Initial Render)

처음으로 app이 시작될 때, 리액트는 target DOM 노드와 함께 createRoot()를 호출함으로써 랜더링을 한다.
아래 코드가 그 과정을 보여주는데, 처음 리액트 앱을 세팅할 때 가장 기본이 되는 index.tsx에서 작성하는 코드이다.

여기서 createRoot()브라우저 DOM 노드 내부에 리액트 컴포넌트를 넣을 수 있는 root를 만들어주는 역할을 한다.
다르게 말하면 Virtual DOM Tree에서 <div id='root' />를 나타내는 노드를 생성한다고 볼 수도 있다.
즉, 앞으로 작성할 컴포넌트들이 들어갈 자리를 만들어주는 것!

그 후 반환된 render() 메소드를 통해 브라우저 DOM 노드에 JSX를 표시(display) 한다.
createRoot()가 생성한 root 안에 화면에 그리고 싶은 reactNode를 추가한다고 생각하면 이해하기 쉬울 것이다.

createRoot(domNode, options?)
createRoot()는 인자로 최상단의 노드가 될 domNode와 기타 설정을 위한 options 객체 (optional)를 전달받는다.
그리고 renderunmount 메소드가 포함된 객체를 반환한다.
(options 객체는 쓰일 일이 많지 않으므로 따로 정리하지는 않았다.)



Virtual DOM은 또 뭔데?

위에서 설명 중에서 Virtual DOM Tree라는 개념이 등장한다.
이건 또 뭘까?!?

Virtual DOM은 리액트에서 성능 최적화를 위해 사용되는 메모리 내 가상의 DOM 표현이다.
리액트는 변경사항이 있을 때마다 DOM을 직접 업데이트하는게 아니라,
메모리 상에 존재하는 Virtual DOM을 업데이트한 뒤 필요한 부분만 실제 DOM에 반영하여 랜더링 효율을 높인다.
즉, Virtual DOM은 실제 DOM에 적용하기 전 사전작업을 하기 위한 가상의 DOM이라고 볼 수 있다.

최초 랜더링에 대한 설명에 virtual DOM 개념을 덧붙이면,
createRoot()가 곧바로 DOM 노드에 루트를 생성하는 것이 아니라 Virtual DOM에서 해당 노드를 나타내는 노드(Host Node)를 생성하고 render()를 통해 컴포넌트를 추가한 뒤 DOM 노드에 주입하는 것이다.

리액트는 이 Virtual DOM을 활용하여 화면을 효율적으로 그리며, 그 과정을 Reconciliation이라고 부른다.
Reconciliation은 state 변경으로 인한 랜더링과 함께 설명해보려고 한다.



또또또, 새로운 개념! Reconciliation은 뭔데?

Reconciliation은 리액트에서 컴포넌트의 상태가 변경될 때,
virtual DOM과 실제 DOM 사이의 차이를 계산하고 최적화된 방식으로 DOM 업데이트를 수행하는 과정이다.

리액트는 상태가 변경될 때마다 새로운 가상 DOM 트리를 생성하고,
이전 가상 DOM 트리와 비교하여 어떤 변경사항이 있는지 파악하는데 이 과정을 diffing이라고 부른다.
이후 변경사항을 최소한의 연산으로 실제 DOM에 반영하는 작업을 'commit'이라고 하며, 이 과정을 통해 브라우저의 렌더링 작업을 최소화하여 성능을 향상시키게 된다.

Render Phase : 실제 변경하지 않고 미리 작업만 해보는 것
Commit Phase : 변경된 내용을 실제 브라우저에 적용하는 것

React 15 이하에서는 stack reconciler가 구현되었다고 한다.
현재는 더 지원하지 않는다고 하는데 그 이유는 업데이트를 스택에 쌓아 동기적으로 처리함으로써
오래걸리는 변경사항이 있거나 너무 많은 컴포넌트가 한꺼번에 업데이트 되는 경우 화면이 멈춘 것처럼 보일 수 있기 때문이었다고 한다.
React v.16부터는 fiber reconciler가 그 자리를 대체하고 있다.

*참고사항
Reconciliation을 알고리즘으로, reconciler를 알고리즘을 구현하는 엔진으로 생각하면 조금 더 쉽다.



네버엔딩.. Fiber는 뭔데?

사실 지금까지 소개한 그 어떤 개념보다 Fiber를 이해하고 정리하기가 제일 어려웠다.
여전히 부족한 부분도 많다고 느껴지지만 일단 이해한 대로 정리해보려고 한다.

Fiberstack reconciler를 대체하기 위한 새로운 전략으로, 동기적으로 동작하는 이전 reconciler의 문제를 해결하기 위해 탄생한 개념이다.
Fiber의 핵심 개념들을 정리하면 아래와 같다.

  • Fiber Node : Fiber는 리액트 내의 요소(element)에 대응하는 Fiber node 개체를 사용하며, 이 노드들은 서로 연결되어 트리를 형성한다. (동등한 레벨로 연결된 노드를 sibling, 하위에 있는 노드들을 child, 상위에 있는 부모 노드를 return이라고 부른다.)
  • Unit of Work : Fiber는 리액트 랜더링의 가장 작은 단위로, 변경 사항이 발생했을 때 이루어져야 하는 작업들(work)이 여러 개의 작은 단위로 분할된 것이라고 볼 수 있다.
  • 우선순위 기반 스케줄링 : 스케줄링은 언제 어떤 work가 실행되어야 하는지 결정하는 과정이다. Fiber는 작업을 여러 우선순위로 구분할 수 있으며, 그에 따라 작업을 처리한다.

우선순위 기반 스케줄링을 가능하게 하기 위해서는 아래 4가지가 달성되어야 한다.

  • work를 중단하고, 필요 시 다시 시작할 수 있어야 한다.
  • 다른 종류의 work에 우선순위를 부여할 수 있어야 한다.
  • 완료된 work를 재사용할 수 있어야 한다.
  • 더이상 필요하지 않은 work를 버릴 수 있어야 한다.

Fiber는 작업을 작은 단위로 쪼개고 각 단위의 작업에 우선순위를 부여함으로써 위의 4가지를 모두 가능하도록 했다.

Fiber reconciler는 두 종류의 트리를 가지고 있는데, 하나는 current 트리이고 다른 하나는 workInProgress트리이다.
current Tree는 현재 상태를 보여주며, workInProgress Tree는 모든 변경 사항이 사전에 적용되는 트리이다.
workInProgress Tree에 모든 변경사항이 적용 완료되면 해당 트리가 current Tree가 된다.
이 쯤 오니까 전에 봤던 reconciliation 개념이 보인다..!
Fiber는 queue를 사용하여 업데이트를 처리한다고 하는데, 사실 이 부분은 정확하게 이해하지 못했다.

Fiber에 대해서는 더 방대하게 내용이 많지만,
작은 단위로 쪼개진 작업들에 우선순위를 부여함으로써 변경사항을 더 효율적으로 적용할 수 있게 하는 개념.. 정도로 마무리를 해볼까 한다.

참고자료
[ 리액트 랜더링 & initial render ]
https://react.dev/learn/render-and-commit
https://react.dev/reference/react-dom/client/createRoot
[ virtual DOM & reconciliation ]
https://legacy.reactjs.org/docs/faq-internals.html
https://react.dev/learn/preserving-and-resetting-state
https://ko.reactjs.org/docs/codebase-overview.html
[ fiber ]
https://github.com/acdlite/react-fiber-architecture
https://medium.com/react-in-depth/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react-e1c04700ef6e
https://jasonkang14.github.io/react/what-is-fiber-architecture

profile
성장형 프론트엔드 개발자

0개의 댓글