react-native-seoul
Published in

react-native-seoul

[React] 리액트를 처음부터 배워보자. — 02. React.createElement와 React.Component 그리고 ReactDOM.render의 동작 원리

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

React에 대한 복습과 조금 더 문서를 꼼꼼히 읽는 과정을 글로 옮기기 위해 글을 쓰다보니 생각보다 많은 부분에 대해 고민하고, 정리하게 되었다.

확실히, 안다고 생각하며 넘어가는 것 보다 내가 이해하고 관찰한 것이 맞는지 글로 남겨보는 것이 중요한 것 같다.

지난 글을 쓰고난 후 React의 어떤 부분을 다시 한번 확인해볼지 고민했다. 최근에 Custom Hooks를 만드는 재미에 빠져서, Hooks에 관련된 글을 쓸까 하다가 아직 Hooks가 내부적으로 어떻게 동작하는 지 파악하지 못해, 차근 차근 React.createElementReact.Component에 대한 글을 써볼까 한다.

React.createElement 와 React.Component

React를 JSX(Javascript XML)라는 문법으로 바로 배우게 된다면 React.createElement 라는 기능에 고민 없이 넘어갈 수도 있다. 나도 React 공식 문서를 꼼꼼히 읽으며 가장 놀랐던 점이 이 부분이다. React 공식 문서를 인용하면 다음과 같다.

각 JSX 엘리먼트는 단지 React.createElement()를 호출하는 편리한 문법에 불과합니다.

즉, JSX 문법은 React.createElement() 를 호출하기 위한 하나의 방법일 뿐이고 Babel(Javascript 트랜스파일러)을 통해 파싱되고 트랜스 파일링된다.

Parse(구문분석)는 언어학에서 문장을 그것을 이루고 있는 구성 성분으로 분해하고 그들 사이의 위계 관계를 분석하여 문장의 구조를 결정하는 것을 말한다. 컴퓨터 과학에서 파싱은 일련의 문자열을 의미있는 토큰으로 분리하고 이들로 이루어진 AST(추상 구문 트리)를 만드는 과정이다.

01. React.createElement 엘리먼트

React 엘리먼트는 호스트 객체를 그릴 수 있는 일반적인 자바스크립트 객체입니다.
React 엘리먼트는 가볍고 호스트 객체에 직접적으로 관여하지 않습니다. 단지 화면에 무엇을 그리고 싶은지에 대한 정보가 들어 있을 뿐입니다.

리액트는React.createElement(type, [props], [...children]) 라는 API를 통해 컴포넌트를 생성한다. type 부분에는 React 엘리먼트 타입, HTML 태그 타입, React Fragment 타입 중 하나가 올 수 있다.

내부적으로 createElement API를 보면 인자를 받아서 ReactElement로 만들어주는 것을 확인할 수 있었다. ReactElement는 내부적으로 다음 값을 가지는 Object이다.

즉, 리액트 컴포넌트는React.createElement를 통해 엘레먼트에 대한 정보를 가지는 Object를 생성하고 이를 In-Memory에 저장하고 ReactDOM.render 함수를 통해 Web API(document.createElement)를 이용해서 실제 웹 브라우저에 그려주는 방식으로 동작한다.

브라우저 DOM 엘리먼트와 달리 React 엘리먼트는 일반 객체이며(plain object) 쉽게 생성할 수 있다.
React DOM은 React 엘리먼트와 일치하도록 DOM을 업데이트
한다.

리액트에서는 엘리먼트가 모여 컴포넌트라는 구조를 만든다.

02. React.Component

리액트에서 재사용 할 수 있는 UI를 하나의 묶음으로 컴포넌트라고 한다. 즉, 엘리먼트의 조각들을 모아 모듈화 한 것을 의미한다.

리액트 컴포넌트에는 Functional ComponentClass Component 두 종류가 있는데, 이 둘의 구분에 대해서는 다음 글에서 작성할 것이다.

React 내부 코드에서 Component는 위와같이 기술된다. 즉, 컴포넌트는 Props와 State를 가지는 Object이다.

해당 부분에 대해 코드를 읽으며 흥미로운 점은 setState에 대한 부분이다. setState는 Component의 Prototype으로 다음과 같이 구현되어있다.

즉, updater안의 state를 업데이트 하는 Queue 구조의 setState 이벤트 큐가 있고 이 state를 변경하는 이벤트 큐에 변경된 state를 넣어 업데이트가 이루어진다.

리액트 컴포넌트는 Functional인지 Class인지에 따라 베이스는 같지만 Prototype이 다르던가 Updater가 다른 것 같다.

이 부분이 조금 흥미가 생겨서 ClassComponent의 Updater를 살펴보았다. ClassComponen 에서 enqueueSetState는 다음과 같이 구현되었다.

즉, 리액트의 업데이트는 enqueueUpdate 내부에서 공유되어진(Shared) 전체 업데이트 이벤트 큐가 있고, 해당 작업들이 큐에서 순차적으로 이루어지며 Root에 반영되는 구조임을 알 수 있었다.

Root에 변경을 반영하는 코드는 scheduleUpdateOnFiber에서 확인할 수 있다. markUpdateLaneFromFiberToRoot 라는 함수에서 해당 부분을 구현한다.

03. ReactDOM.render

대부분, CRA(Create-React-App)을 통해 리액트 프로젝트를 생성하면, 다음과 같은 코드를 볼 수 있다.

ReactDOM.render 코드는 React 컴포넌트를 웹 브라우저에 렌더링 하는 API로 그 내부 구조가 다음과 같다.

코드가 복잡하지만 파악할 수 있는 부분은Dom ElementcontainerReact Elementelement를 인자로 받아서 ContainerSubTree로 리액트 엘리먼트를 Render 한다는 것을 파악할 수 있다.

위 사실을 통해 우리는 결국, React Elementroot라는 id를 가지는 div DOM Element의 아래에 SubTree형태로 넣어준다는 것을 알 수 있다.

그 후, 자체적으로 DOM Element의 속성으로 _reactRootContainer를 넣어주고, 그 내부 _internalRoot.current 안에 자식 노드들을 추가함으로 써 Virtual DOM 트리를 구현한다.

해당 부분을 좀 더 직관적으로 알기 위해서 리액트로 만들어진 Velog를 들어가서 콘솔에 document.getElementById(“root”)._reactRootContainerdocument.getElementById(“root”)._reactRootContainer._internalRoot.current 를 쳐서 그 내부 객체에 대해 확인해보도록 하자.

마지막으로 updateContainer를 파악하도록 하자. 이 부분에서 React.Component 의 setState의 동작원리에서 발견한 enqueueUpdatescheduleUpdateOnFiber 를 만날 수 있었다.

기회가 되면 enqueueUpdatescheduleUpdateOnFiber에 관한 글도 써봐야겠다. 그 내부를 열어보니 여기에 가볍게 담기에 그 양이 많았기 때문이다.

결국, React 는 큰 틀에서 다음과 같은 특성을 가진다는 것을 파악할 수 있었다.

01. 컨테이너 DOM Element 안에 자체적인 DOM 트리를 만든다.
02. 그 안의 컴포넌트들의 변경 사항이 있으면, updateQueue에 등록한다.
03. 해당 updateQueue의 변경 사항들이 배치처리를 통해 실행된다.

맺음말

이번에는 React의 엘리먼트, 컴포넌트 개념과 ReactDOM.render가 어떻게 동작하는가 대해 배워보았다. 결국 React의 엘리먼트는 Object이고 엘리먼트가 모여 컴포넌트를 이룬다는 것을 알게 되었다.

또한, 리액트 컴포넌트는 Updater라는 속성을 가지는데 해당 부분에서 업데이트 관련 이벤트 큐가 있고 순차적,배치적으로 UI의 업데이트가 이루어짐에 대해 확인하였다.

React 를 쓴 지 1년이란 시간이 되었는데 React 동작 코드를 열어서 어떻게 동작 하는지 직접 확인하니 그 동안 어렴풋하게 알던 개념이 조금씩 명확해지게 되어서 좋은 경험이 되는 것 같다.

다음 글로는 Functional Component와 Class Component의 차이에 대해서 작성할 것이다.

참고 자료

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store