Do as I say, not as I do

[React] 리액트를 처음부터 배워보자. — 03. React 의 Update 스케줄링 과정

Stark Studio
Cross-Platform Korea
10 min readJun 2, 2020

--

이 글을 남기는 계기는 Dan Ambramov의 블로그 Overreacted를 보며 React.js에 대해 더 깊이 공부할 필요성을 느꼈기 때문이다.

이번 글은 지난 글을 읽고 나에게 피드백을 주신 고마운 분이 알려준 자료를 보고 React의 Update 스케줄링 과정에 대해 알아보는 글이다.

해당 글은 리액트를 처음 접하는 사람이 읽기에 다소 어려울 수 있지만, 나와 같이 리액트를 배우며 “도대체 어떻게 동작하는거야?” 라는 생각이 드는 개발자들에게 도움이 될 것이라 생각한다.

지난 글을 통해 리액트 내부 코드를 열었을 때, ReactDOM.render API가 리액트 컴포넌트 트리를 가지고 렌더링 큐(업데이트 큐)에 등록하는 방식으로 구현된다는 사실을 알 수 있었다.

이 사실을 알았을 때 나는 자연스레 다음과 같은 생각을 하였다.

업데이트 큐를 주기적으로 실행해주는 스케줄러가 있고, 특정 시점마다 이 업데이트 큐의 작업을 수행하는 것이 아닐까?

위와 같은 이 질문에 내 글을 읽어주신 고마운 분이 다음과 같은 말을 하시며 2개의 자료를 주었다.

React 내부의 업데이트 큐는 16ms마다 window.requestIdleCallback()을 통해서 실행되어요. window.requestIdleCallback() API가 없으면 내부적으로 구현한 로직으로 스케줄 작업을 합니다.

01. Work Loop와 Fiber Tree

React Element는 다음과 같은 형태의 객체로 저장하며 이를 ReactDOM.render를 통해 해당 엘리먼트에 대한 작업을 실행한다.

const element = {
type: "h1",
props: {
title: "hello",
children: {
...
}
}
}

이때 React는 어떻게 DOM Tree 를 렌더링하고 업데이트 할까? React 코드를 분석 중 컨테이너의 WorkLoop라는 부분과 ReactUpdateQueue 부분에서 이 과정을 수행한다는 것을 알 수 있었다.

이에 대해 깊이 파악하기 전에, Rodrigo Pombo의 Build your own React 글에서 얻은 Fiber Tree의 개념에 대해 간략하게 소개한다. 리액트에서는 React 컴포넌트를 렌더링 하기 위해 다음 3가지 과정을 반복한다.

01. Element를 DOM에 추가한다.
02. Element의 children(자식노드)을 추가하기 위한 Fiber(작업)를 생성한다.
03. 다음으로 진행되어야 할 작업을 선택한다.

다음 진행할 작업은 child(자식노드)가 있을 때는 이를 먼저 수행하며, 그 이후에 sibling(형제 노드)를 다음 작업으로 지정한다.

또한, 자식 노드를 수행할 때에도 형제 노드에 대한 작업이 끝나면 부모 노드의 형제 노드(삼촌 노드)로 다음 작업을 할당한다.

위의 부모-자식-형제-삼촌 순서로 순회하는 React의 특성으로 인해 상위 컴포넌트가 렌더링 하면 그 아래의 컴포넌트들도 다시 렌더링 되는 것 같다.

위의 과정을 Root에서 시작해 다시 Root로 돌아온다면 React의 render 과정이모두 끝난 것이라고 판단한다.

02. requestUpdateLane 과 enqueueUpdate

위의 과정을 통한 React의 동작 원리를 파악하기 전에 React의 Root 엘리먼트는 current라는 항목을 갖는다는 사실을 파악했다. 이 부분에 Root 엘리먼트의 child와 React와 관련된 다양한 값이 보관되는 것을 확인할 수 있었다.

legacyCreateRootFromDOMContainercreateLegacyRootReactDOMBlockingRootcreateRootImpl createContainercreateFiberRootnew FiberRootNode 코드에서 React Root에 current를 초기화 하는 것을 확인할 수 있다.

current는 updateContainer에서 requestUpdateLaneenqueueUpdate, scheduleUpdateOnFiber 등에서 인자로 사용된다.

requestUpdateLane은 React Update에 대한 우선 순위를 반환하는 코드였다.
enqueueUpdate 는 Queue구조에 대해 배워볼 수 있는 함수였는데 다음과 같은 코드를 가진다.

React의 업데이트 큐는 우선 순위가 지정된 업데이트의 Linked List이며 Current Queue, Work-In-Progress Queue이중 버퍼 구조를 가진다고 한다.

위의 원리가 다소 어려워 내가 이해하는 수준으로 글을 쓰려고 한다. 혹시 이 부분에 대해 깊이 아는 사람이 있다면, 글에 기록을 남기길 바란다.

Facebook에서는 이 부분을 다음과 같이 설명한다.

만약, Work-In-Progress(진행중인) 렌더링이 완료되기 전에 폐기(Discarded)가 되면 Current Queue를 복사해서 새로운 Work-In-Progress Queue를 생성한다.

즉, React는 업데이트에 대한 것을 2개의 버퍼를 가지고 진행한다.
이렇게 구현하는 이유는 React의 업데이트가 진행될 때 중간에 렌더링이 폐기될 경우에도 업데이트를 유지하기 위함이라고 한다.

03. scheduledUpdateOnFiber

해당 함수에서 내가 이 글을 쓰는 이유를 찾을 수 있었다.

scheduledUpdateOnFiberperformSyncWorkRootrenderRootSyncworkLoopSyncperformUnitOfWorkbeginWorkcompleteUnitOfWork completeWork 에서 그 동안 가졌던 React 컴포넌트에 대한 궁금증이 약간 해결되었다.

scheduleUpdateOnFiberenqueueUpdate에서 추가한 업데이트를 스케줄러에 반영하여 실질적인 업데이트 반영을 진행하는 기능을 한다.

이 부분에서 performSyncWorkOnRoot를 자세히 들어가면

위와 같이 Fiber(WorkInProgress)의 작업을 수행하는 performUnitOfWork 가 반복문으로 돌아가고 있음을 확인할 수 있었다.

React는 보다 더 복잡한 방식으로 돌아가지만 결국 주기적인 Fiber의 생성 → 단위 작업 처리와 작업 스케줄링의 반복인 것을 확인할 수 있었다.

결론

이 글을 통해 React DOM Tree(Fiber Tree)는 다음과 같은 동작을 반복한다는 것을 알게되었다.

01. Element를 DOM에 생성
02. Element의 children(자식노드)을 추가하기 위한 새로운 Fiber(작업)를 생성
03.다음으로 업데이트나 렌더링 할 작업을 DOM 순회 순서대로 결정

updateContainer의 코드에서 위의 작업이 반복되는 구간인 enqueueUpdate scheduleUpdateOnFiber 함수에 대해서 파악해 볼 수 있었다.

위의 3가지 간략한 방법을 위해 React 내부 코드에서는 다양한 함수들이 구현되어 있었고 체계적으로 연결되어 있다는 것을 파악했다.

각 함수들이 세부 사항들을 위해 잘 분리되고 정리된 것을 보며 React 의 원리 외에도 프로그래밍을 하는 법에 대해 많이 배울 수 있었다.

맺음말

내가 처음 React를 접했을 때 이미 v16 이상이었다. 구글에서 해당 부분에 대해 공부하니 v16이전에는 React Stack 알고리즘으로 Reconciliation(재조정)을 진행했다고 한다.

다만, 현재 이용하는 v16이상 부터 React에서 React Fiber라는 새로운 Reconciliation(재조정) 알고리즘을 적용해 기존의 Reconciliation 알고리즘에서 발생하는 애니메이팅에 대한 부분을 개선했다고 한다.

이렇게 리액트에 대한 전반적인 로직에 대해 파악하니 개발을 진행하면서 발생하는 렌더링 이슈에 대해 조금 더 차근차근 확인할 수 있게 되었다.

다음 글로는 React Hooks의 원리에 대해 쓰면서 그 동안 쓰고는 있었지만 그 원리에 대해 완벽히 파악하지 못한 Hooks에 대해 깊게 파악해보려 한다.

React Fiber 에서는 JS함수와 DOM 객체를 Fiber(섬유)라고하며 각각 개별적으로 조작 및 업데이트 할 수 있다.
또한, 현재 React에서는 내부적으로 구현한 Scheduler를 사용하고 있다.

참고자료

--

--