3 minute read

부스트캠프에서 마스터로 활동하고 계신 황준일님이 쓰신 Vanilla Javascript로 가상돔(VirtualDOM) 만들기를 먼저 읽고 읽으면 이해하기 수월합니다.

1. 개발 배경

바닐라 자바스크립트로 프로젝트를 수행하게 되어 Web Components에 styled-components를 모방한 방식으로 스타일을 적용하여 아래와 같이 사용하고 있었습니다.

  • index.html
  • src/MyElement.js
  • src/StyledComponents.js

순수한 자바스크립트 파일에서는 JSX 문법을 사용할 수 없어 템플릿 리터럴을 중첩해 사용하는 것에 불편을 느끼던 와중에 JSX 문법을 컴파일 해주는 babel이라는 도구를 발견했습니다.

2. babel을 이용한 컴파일

babel을 사용해 JSX 문법을 컴파일하기 위해서는 @babel/plugin-transform-react-jsx라는 플러그인을 사용합니다. 자세한 내용은 여기에서 보실 수 있지만 간략하게 정리하고 넘어가겠습니다. 우선, 아래와 같은 명령어로 npm 패키지를 설치해야 합니다.

npm install --save-dev @babel/core @babel/cli @babel/plugin-transform-react-jsx

다음으로 babel.config.json파일을 만들어 플러그인 설정을 합니다.

{
	"plugins": ["@babel/plugin-transform-react-jsx"]
}

src 디렉토리의 파일을 컴파일한 결과가 lib 디렉토리에 생성되게 하려면 CLI에서 아래 명령어를 입력하면 됩니다.

npx babel src --out-dir lib

package.json 파일에 scripts로 등록해 놓고 사용하면 편리합니다.

{
  "scripts": {
    "build": "babel src --out-dir lib"
  }
}

이것으로 환경 설정은 모두 끝났습니다. 하지만 JSX 문법으로 작성된 파일을 컴파일하기 위해서는 아래와 같은 시그니처를 갖는 함수가 필요합니다. 이 함수의 역할은 Virtual DOM을 만들어주는 것입니다.

function h(type, props, ...children) {
	return { type, props, children: children.flat() };
}

또한, 컴파일할 파일 상단에 다음과 같은 주석을 달아 주어야 합니다.

/** @jsx h */

아래처럼 사용할 수 있습니다.

위 파일을 컴파일한 결과는 아래와 같습니다.

여기서 주목할만한 점이 있습니다. 바로 Wrapper, Title 같은 변수가 h 함수에 첫 번째 인자로 전달된다는 것입니다. 만약 이 변수가 함수라면, 이 함수에 props와 children을 인자로 넣어 호출해 준다면 React의 함수형 컴포넌트를 만들 수 있지 않을까라고 생각했습니다.

3. 함수형 컴포넌트 만들기

(1) 폴더 구조

다음과 같은 구조로 폴더를 구성해보겠습니다.

.
├── babel.config.json
├── index.html
├── package.json
├── src
│   ├── App.js
│   ├── components
│   │   ├── Article.js
│   │   ├── ArticleList.js
│   │   └── Header.js
│   ├── core
│   │   ├── internal
│   │   │   ├── dom.js
│   │   │   └── root.js
│   │   ├── react-dom.js
│   │   └── react.js
│   └── index.js
└── static
    └── css
        └── index.css

(2) 설정 파일 및 정적 파일

  • package.json
  • babel.config.json
  • index.html
  • static/css/index.css

(3) React와 ReactDOM

Virtual DOM을 만들어주는 함수를 h라는 이름 대신 React.createElement라는 이름으로 만들어주겠습니다.

  • src/core/react.js

ReactDOM.render 함수는 루트 엘리먼트와 루트 컴포넌트를 설정하고 처음으로 렌더링을 합니다.

  • src/core/react-dom.js

(4) Virtual DOM을 실제 DOM으로

root.js는 루트 엘리먼트와 루트 컴포넌트를 변수로 가지고 있습니다.

  • src/core/internal/root.js

여기서 $root은 실제 DOM의 element이고, rootComponent는 Virtual DOM입니다. 따라서 Virtual DOM을 실제 DOM의 element로 만들어주는 과정이 필요합니다. 이를 위해 dom.js에 _createElement라는 함수를 만들었습니다.

  • src/core/internal/dom.js

핵심적인 부분은 node의 type이 함수라면 props와 children을 담은 객체를 인자로 하여 해당 함수를 호출하고, 그 반환값을 다시 _createElement에 재귀적으로 전달하는 것입니다.

(5) 컴포넌트 개발

코어 로직은 완성되었습니다. 이제 함수형 컴포넌트를 사용할 수 있습니다. 우리가 만든 함수형 컴포넌트는 위에서 전달한 props를 인자로 받습니다. children prop을 이용하면 컴포넌트의 composition이 가능합니다.

  • src/components/Header.js

Article 컴포넌트는 title, author, content를 props로 받아 렌더링합니다.

  • src/components/Article.js

ArticleList 컴포넌트는 props로 전달받은 배열을 순회하며 Article 컴포넌트를 렌더링합니다.

  • src/components/ArticleList.js

(6) Entry Point

App 컴포넌트는 위에서 만든 Header 컴포넌트와 ArticleList 컴포넌트를 사용합니다.

  • src/App.js

index.js는 위에서 만든 ReactDOM.render 함수에 루트 엘리먼트와 루트 컴포넌트를 전달합니다.

  • src/index.js

(7) 빌드

터미널에서 npm run build 명령어를 입력하면 아래와 같이 static 폴더 아래에 컴파일된 자바스크립트 파일들이 생성됩니다.

.
├── babel.config.json
├── index.html
├── package.json
├── src
│   ├── App.js
│   ├── components
│   │   ├── Article.js
│   │   ├── ArticleList.js
│   │   └── Header.js
│   ├── core
│   │   ├── internal
│   │   │   ├── dom.js
│   │   │   └── root.js
│   │   ├── react-dom.js
│   │   └── react.js
│   └── index.js
└── static
    ├── css
    │   └── index.css
    └── js
        ├── App.js
        ├── components
        │   ├── Article.js
        │   ├── ArticleList.js
        │   └── Header.js
        ├── core
        │   ├── internal
        │   │   ├── dom.js
        │   │   └── root.js
        │   ├── react-dom.js
        │   └── react.js
        └── index.js

(8) 실행

Repository

본 게시글에서 사용된 코드는 이곳에서 확인하실 수 있습니다.

Reference

Leave a comment