Amada Coding Club

[React Core Deep Dive] 1. Overview 본문

Mash-Up/Study

[React Core Deep Dive] 1. Overview

아마다회장 2024. 4. 2. 00:28

이번 Mash-Up 14기로 활동하면서 스터디에 참여하게 되었다.

내가 참여한 스터디는 RCDD, React Core Deep Dive 스터디.

즉, 리액트 내부 코드에 대해 깊이 들어가 탐구하는 스터디다. 

 

리액트가 어떻게 작동되고 움직이는 지에 대해 깊숙하게 이해하는데에 목적을 두었다.

이 스터디를 제안해준 형이 찾은 블로그가 있는데

https://jser.dev/series/react-source-code-walkthrough

 

React Internals Deep Dive

A live series of JSer deep diving into React internals, by reading the actual React source code. This series helps you understand how React works internally and write better React code.

jser.dev

이 블로그의 내용을 번역 및 요약하기로 했다. (번역 및 사용을 허락해주신 원작자님께 감사의 말씀을..)

이번에는 첫 시작이니 Overview를 번역했다. 

 

Overview의 내용은

1. 리액트 내부를 공부하는 방법

2. 중단점을 통해 리액트 내부를 디버깅하는 방법

3. 리액트 내부에 대한 전반적 개요

로 구성되어 있다. 

 

1. 리액트 내부를 공부하는 방법

(1) 공식 자료 이해하기

 

React.dev 에서는 React API를 제공해줄 뿐만 아니라 React 팀에서 이 API를 왜 사용하고 왜 제작했는지, 혹은 왜 제거했는지에 대한 내용을 자세하게 볼 수 있다. 이를 통해 React 팀의 생각을 볼 수 있고 어느 게시글보다도 자세한 설명을 볼 수 있다. 

 

(2) 리액트 팀 팔로우 하기

 

React 멤버 의 계정을 모두 팔로우하며(깃허브든, 인스타든,,) 현재 그들이 어떤 생각을 가지고 있고 어느 부분을 중점적으로 개발하고 있는지에 대해서 파악할 수 있다 또한, React 팀원끼리 인터넷에서 논의하는 내용은 코드에서는 생각할 수 없는 색다른 관점을 제공할 수도 있다! 

 

(3) React 레포지토리 이해하기

 

React 레포지토리 에는 코드뿐만 아니라 코드를 개선하고자 하는 PR과 코드 리뷰를 볼 수 있다. 이곳에서 코드의 주석보다 더 나은 설명이 있을 경우가 많다!

 

(4) 게시물이 아닌 코드를 신뢰하기

 

React Core에 대한 내용은 인터넷에 찾아보면 많이 찾아볼 수 있다. 그러나 대부분의 게시글이 코드를 직접 분석하기보다 이론적인 내용을 바탕으로 접근하는 경우가 많다. 그래서 React Core에 대한 지식 수준을 확인할 수 있는 React Core Quiz 사이트가 있는데 이론적인 바탕으로만 공부한 경우에는 퀴즈의 문제를 풀기 어렵다. 그래서 코드를 직접 찾아보며 공부를 하는 방향이 바람직하다!

 

(5) 주요 경로 찾기

 

그러나.. React는 너무 거대한 코드다.. 그래서 모든 코드를 다 보기는 불가능하다. 그래서 코드를 볼 때 찾고자 하는 부분의핵심적인 코드와 경로를 찾는 것이 중요하다. 예를 들어 렌더링이 발생할 때 어떤 함수들이 순서대로 호출되고 각 함수의 역할은 무엇인지만 이해하면 되고 그 이외의 코드는 이해할 필요가 없다! 

 

그러면 그건 어떻게 찾으면 될라나...

 

2. Breakpoints를 사용해 React Core Overview를 뜯어보기

바로 breakpoint(중단점)을 이용하면 된다!

React Core Overview Demo

위 사이트를 통해 breakpoint를 테스트할 수 있다. 해당 사이트의 코드는 다음과 같다. 

function App() {
  const [count, setCount] = useState(1);
  debugger;

  useEffect(() => {
    debugger;

    setCount((count) => count + 1);
  }, []);
  return <button>{count}</button>;
}
ReactDOM.createRoot(document.getElementById("container")).render(<App />);

debugger가 중단점 역할을 한다. 

 

위 사이트에서 중단점을 테스트하기 전에

container의 break on의 subtree modification을 체크한다(해당 기능은 DOM의 하위 값이 변경되면 중단점을 설정하도록 하는 체크다) 그리고 개발자도구를 킨 상태에서 새로고침을 하게 되면

디버깅을 할 수 있다. 

 

(1) 렌더링 시 첫 번째 일시정지

첫 번째 일시정지

우측의 call stack을 살펴보면 어떤 함수가 차례대로 실행되는지 확인할 수 있다. 

아래에서 위로 차례로 실행된다고 생각하면 편하다.

위 call stack 중에서 중요한 기능은 다음과 같다. 

  • ReactDOMRoot.render: 우리가 작성한 User-side 코드. createRoot가 먼저 호출된 다음에 render가 호출된다.
  • scheduleUpdateOnFiber: 리액트에게 렌더링될 위치를 알려준다. (첫 마운트 때는 이전 버전이 존재하지 않기 때문에 root를 호출한다. )
  • ensureRootlsScheduled: performConcurrentWorkOnRoot라는 함수가 실행 예약이 되도록 보장하는 아주 중요한 함수
  • scheduleCallback: React Scheduler의 일부인 실제 스케줄링은 위 스크린샷의 postMessage를 통해 비동기로 작동하는 것을 확인할 수 있다. (그걸 어떻게 scheduleCallback 이 비동기라는 걸 확인할 수 있는지..? 이건 잘 모르겠다. )
  • workLoop: React Scheduler가 태스크를 실행하는 방법임
  • performConcurrentWorkOnRoot: 아까 ensureRootlsScheduled에 의해 예약된 함수로 컴포넌트가 실제로 렌더링된다. 

이 몇 가지 함수를 통해 React가 실제로 어떻게 렌더링을 수행하는지 몇 가지 단서를 파악할 수 있다. 

 

(2) DOM 조작 시 두 번째 일시정지

DOM을 업데이트 할 때 발생하는 두 번째 일시정지..

이 단계는 render 다음 단계인 commit 단계에 해당한다. React의 UI 라이브러리로서의 목표는 DOM 업데이트를 관리하는 것이다. 위 call stack에서 commit 단계에 해당하는 함수를 확인할 수 있다. 

 

  • commitRoot: render 단계에서 발생한 필요한 DOM 업데이트를 commit 하고 이펙트 처리와 같은 많은 작업을 수행한다. 
  • commitMutationEffect: DOM 실제 업데이트 작업을 수행한다. 

(3) Effect 실행 시 세 번째 일시정지

세 번째 일시정지

다음으로는 useEffect 호출 시 일시중지가 발생한다. 참고할 만한 함수는 다음과 같다. 

  • flushPassiveEffects: useEffect에 의해 생성된 모든 패시브(수동적인?)이펙트들을 플러시한다

또한, 위 함수는 postMessage에 의해 비동기화되어서 예약되고, flushPassiveEffect에 breakpoints를 추가하면 해당 지점이 commitRoot 내부에 있음을 확인할 수 있다. 

비동기임을 확인할 수 있다.

(4) 렌더링 컴포넌트에서 다시 한 번 일시정지

4번째 일시정지

useEffect에서 setState를 호출했기 때문에 다시 렌더링을 실행하는데, 첫 번째 일시정지와 유사해보이지만 , performConcurrentWorkOnRoot 내부에서는 mountIndeterminateComponent가 아닌 updateFunctionComponent를 호출한다는 점에 차이가 있다. mountIndeterminateComponent는 첫 렌더링 때만 실행되고 그 이후에는 이전 버전이 존재하기 때문에 updateFunctionComponent를 호출한다. 

 

4. React Core Overview

출처:&nbsp;https://jser.dev/2023-07-11-overall-of-react-internals

원본 블로그에서 가져온 사진과 같이 리액트는 trigger, schedule, render, commit 4단계로 나눌 수 있다. 

 

(1) Trigger

작성자는 모든 작업의 시작이 이곳에서 일어나기 때문에 트리거라는 이름을 사용했다고 한다. 초기 마운트, 리렌더링에 관계없이 실행되며 앱의 어느 부분에 렌더링이 필요한지, 어떻게 렌더링이 되어야 하는지 React 런타임에 전달한다. 즉, 이 단계는 React가 해야할 일을 만드는 단계라고 생각하면 쉽다. 

 

해당 작업은 ensureRootlsScheduled을 마지막으로 performConcurrentWorkOnRoot 가 실행되도록 예약하는 작업을 하고 scheduleCallback을 통해 태스크가 Scheduler 단계로 넘어간다. 

 

(2) Scheduler

스케줄러는 말 그대로 우선 순위에 따라 작업을 처리하는 우선순위 큐를 의미한다. scheduleCallback을 통해 렌더링이나 이펙트 작업을 예약하고 workLoop에 의해 예약된 작업이 실행한다. 

 

(3) Render

Render 단계는 예약된 작업( performConcurrentWorkOnRoot  )을 실행하는 단계라고 생각하면 쉽다. 여기에서 새로운 Fiber tree를 계산하고 DOM에 어떤 업데이트가 필요한지 파악하는 작업을 실행한다. Fiber Tree는 과거 가상 DOM으로 불리던 트리로 앱의 현재 상태를 나타낸다. (일단 여기에서는 이정도만 알면 된다.)trigger 단계에서 생성된 performConcurrentWorkOnRoot가 스케줄러에서 우선순위를 할당받고 Render 단계에서 실행된다!

 

(4) Commit새로운 Fiber tree에서 최소한의 업데이트가 계산되면 DOM에 그 업데이트된 내용을 Commit한다. 여기에는 DOM 조작(commitMutationEffects)

모든 종류의 effect 처리(flushPassiveEffects, commitLayoutEffects) 가 포함된다. 

 

총정리

오늘은 전반적인 React 내부 로직의 개관을 살펴보았다. 

1. 내부를 관찰하기 위해선 어떤 방법을 사용하는지

2. 중단점을 사용한 내부 관찰

3. React 내부의 단계

이렇게 세 가지를 다뤘다. 

 

디버깅 영상 에서는 실제 React 코드를 가지고 hello world를 화면에 띄울 때 각 단계별로 실행되는 함수들에 대해서 소개하는 내용을 가지고 있다. 정리하기엔 나에게는 아직 벅찬 것 같다. 차근차근하면 안될 건 없다. 불가능은 없다.

 

앞으로 정리를 하며 앞에 내용에서 간략하게 넘어간 함수나, Fiber tree에 대해 Deep하게 들어갈 계획이다. 

 

Reference

https://jser.dev/2023-07-11-overall-of-react-internals

 

How does React work under the hood ? The Overview of React internals

I'm going to give you a rough overview of React internals and also some tips on how to learn React internals

jser.dev