리액트를 사용하면서 무심코 넘어갔던 렌더링 방법에 대해 정리해보고자 한다.
일상적으로 '렌더링 한다'
라고 하면 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
재미있어요!