리액트를 사용하면서 무심코 넘어갔던 렌더링 방법에 대해 정리해보고자 한다.
일상적으로 '렌더링 한다' 라고 하면 HTML, CSS, JavaScript 를 가지고 화면을 그리는 과정을 의미한다.
하지만, 리액트에서의 렌더링은 화면을 그리는데 필요한 DOM Tree 를 구성하는 과정을 뜻한다.
리액트는 자체 렌더링 프로세스를 가지고 있어 성능적으로 좋은 퍼포먼스를 내게 해준다. 😃
렌더링 프로세스는 크게 3개의 단계로 나눌 수 있다.
1. 트리거 단계 (Trigger Phase)
2. 렌더 단계 (Render Phase)
3. 커밋 단계 (Commit Phase)
각 단계에서 하는 작업에 대해 알아보자❗️
트리거 단계는 렌더링이 발생하는 시점의 단계이다.
리액트에서 렌더링이 발생하는 조건은 크게 두가지로 나눌 수 있다. 🤓
사용자가 사이트에 처음 진입하면서 발생하며 서버에서 리소스를 받아온다.
초기 렌더링 이후 발생하는 모든 렌더링을 리렌더링 이라 한다.
useState() 의 setter의 실행으로 state 값이 변경 되는 경우
부모 컴포넌트에서 전달받은 props 값이 변경되는 경우
부모 컴포넌트가 리렌더링 되면 자식 컴포넌트들은 전부 리렌더링 된다.
✤ Class 컴포넌트는 제외
트리거가 발생하면 DOM에 그려질 컴포넌트들을 호출 하는 과정이다.
이 결과로 컴포넌트 정보가 담긴 React Element 의 집합체인 Virtual DOM 을 만든다. 📑
컴포넌트들은 주로 JSX 구문으로 작성되며, JavaScript 가 컴파일되고 배포 준비가 되는 시점에 React.createElement() 호출로 변하게 된다. 😎
createElement 는 일반적인 JS 객체 형식의 React Element 를 반환하는데, 이 엘리먼트는 생성하고자하는 UI 정보에 대한 객체 값이다.
// 개발된 컴포넌트는
return <SomeComponent a={42} b="testing">Text here</SomeComponent>
// 컴파일 이후 아래와 같은 호출식으로 변환된다.
return React.createElement(SomeComponent, {a: 42, b: "testing"}, "Text Here")
// 호출된 이후에는 React Element로 나타난다.
{type: SomeComponent, props: {a: 42, b: "testing"}, children: ["Text Here"]}
서버에서 리소스를 받아와 ReactDOM의 render() 함수를 실행시시켜 root 컴포넌트를 호출한다.
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<App />
);
render() 함수가 호출되면 리액트는 createElement()로 <APP/> 컴포넌트를 React Element로 만든다.

💡초기 렌더의 경우 만들어지는
Virtual DOM이Actual DOM과 동기화 된다.
상태 업데이트가 발생한 컴포넌트만 호출 하고, 변경된 부분만 계산하게 된다.
만약, 자식 컴포넌트가 존재하면 재귀적으로 호출한다.
컴포넌트 호출이 완료되면, 생성된 React Element 들을 모아 Next Virtual DOM 을 생성한다.

만들어진 Next Virtual DOM은 Diffing(재조정) 과정을 통해 Prev Virtual DOM 과 어떤 요소와 속성들이 변했는지를 파악하고
커밋 단계에서 처리한다.
Diffing 에관한 자세한 설명은 여기 를 참고하자 ❗️
렌더 단계에서 발견한 변경 사항들을 Actual DOM 에 적용하는 단계이다.
이때, 변경사항이 있는 노드만 반영한다. 🫢
Actual DOM 이 변경되면 브라우저는 이를 감지하고 이 과정
을 반복한다.

리액트 렌더링 시스템이 이렇게 설계된 이유는 Repainting 작업을 최소화하기 위해서이다.
동시에 발생한 업데이트를 모아(in 가상돔) DOM 을 한번만 업데이트 시키기 때문에 빠른 속도로 화면이 렌더링 될 수 있다. 👍🏻
참조
NE(O)RDINARY CONFERENCE - 이정환
moonkorea
@coddingyun
minsug
재미있어요!