본문 바로가기

React.JS

react 직접 만들어 보기(1)_가상돔 만들기

우아한 형제들 김민태님의 강의를 듣고 직접 정리한 콘텐츠 입니다.


가상돔을 만들기 위해 먼저 리액트의 jsx 문법을 파싱할 수 있는 babel환경을 구축해야 한다.

npm install @babel/cli @babel/core @babel/preset-react

package.json

{
  "name": "tiny-react",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "babel src -d build -w",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@babel/cli": "^7.12.8",
    "@babel/core": "^7.12.9",
    "@babel/preset-react": "^7.12.7"
  }
}

scripts 명령어에 babel 빌드 명령어를 넣어 npm run build를 통해 파싱된 jsx의 결과물을 얻을 수 있다. 

 

src/react.js

export function createElement(tagName, props, ...children) {
    return {tagName, props, children}
}

src폴더에 react.js 파일을 만들고 바벨에서 사용하는 jsx 파싱 함수를 구현한다. createElement함수는 세번째 인자를 가변인수로 받는다. 왜냐하면 html 태그의 자식의 자식들이 계속 들어 올 수 있기 때문이다.

 

src/index.js

/* @jsx createElement */
import {createElement, render} from './react.js';

function Title (props) {
    return (
        <div>
            <h2>바벨 트렌스 파일링 전</h2>
            <h3>안녕하세요</h3>
        </div>
    )
}

render(<Title/>, document.querySelector('#root'));

src/index.js에 jsx로 간단한 리액트 컴포넌트를 만들어 본다.

 

build/index.js (npm run build->바벨 트렌스파일링 결과)

import { createElement, render } from './react.js';

function Title(props) {
  return createElement("div", null, 
    createElement("h2", null, "\uBC14\uBCA8 \uD2B8\uB80C\uC2A4 \uD30C\uC77C\uB9C1 \uC804"),
    createElement("h3", null, "\uC548\uB155\uD558\uC138\uC694"));
}

console.log(Title());
render(createElement(Title, null), document.querySelector('#root'));

npm run build를 하면 트렌스파일링 결과가 build폴더에 생긴다. 참고로 createElement의 첫번째 인자로 스트링이 넘어올수도 있고 함수가 넘어올수도 있다. 그 구분은 첫번째 인자가 jsx의 빌트인 컴포넌트인지 사용자가 만든 컴포넌트인지에 따라 달라지게 된다. 어떻게 바벨의 jsx 컴파일러가 같은 태그<> 형태임에도 사용자가 만든 컴포넌트와 빌트인 컴포넌트를 구별할 수 있을까? 리액트의 공식문서를 보면 사용자가 만든 컴포넌트는 대문자로 시작하라고 되어 있다. 결국 바벨의 jsx컴파일러는 대문자와 소문자로 시작하는 태그를 구별하여 스트링 혹은 함수자체로 createElement로 넘겨줄 수 있는 것이다. 여기서는  빌트인 html태그들이 넘어 왔기 때문에 스트링으로 들어온 것이다.

 

src/react.js (사용자 컴포넌트 구별 추가)

export function createElement(tagName, props, ...children) {
    if (typeof tagName === 'function') {
        return tagName.apply(null, [props, ...children])
    }
    return {tagName, props, children}
}

사용자가 만든 컴포넌트이면 대문자로 시작하고 함수로 넘어오기 때문에 createElement메서드에서 타입이 함수일 경우를 위와 같이 구분해준다.  createElement함수로 props와 children이 가변인자로 들어가기 때문에 apply메서드를 통해 가변인자로 값들을 넣어준다.

 

출력결과

{tagName: "div", props: null, children: Array(2)}
children: Array(2)
0:
children: ["바벨 트렌스 파일링 전"]
props: null
tagName: "h2"
__proto__: Object
1:
children: Array(1)
0: "안녕하세요"
length: 1
__proto__: Array(0)
props: null
tagName: "h3"
__proto__: Object
length: 2
__proto__: Array(0)
props: null
tagName: "div"
__proto__: Object