React를 직접 만들어보자 - (1)

이동창·2022년 5월 9일
0

React-From-Scratch

목록 보기
2/6
// (1)
const element = <h1 title="foo">Hello</h1>

// (2)
const element = React.createElement(
  "h1",
  { title: "foo" },
  "Hello"
)

// (3)
const element = {
  type: 'h1',
  props: {
    title: 'foo',
    children: 'Hello',
  },
};

(1)에서 (2)를 만드는 건 babel과 같은 빌드 툴의 역할이고,
우리는 (2)에서 (3)을 만드는 createElement 함수를 만들어보자

만드는 법은 간단하다.
그냥 첫 번째, 두 번째 인자로 type, props를 받고,
나머지는 ...children으로 묶어서 이를 객체로 반환해주면 된다

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children,
    },
  }
}

다만 children이 string이나 number와 같은 원시 객체인 경우에는
텍스트라는 뜻이므로 textElement로 바꿔주어야 한다.

따라서 위에서 만든 createElement에, 각 children이 object인지 검사를 하고
만약 아니라면 createTextElement라는 다른 함수로 처리해주도록 하자.

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child => {
        typeof child === 'object'
        	? child
        	: createTextElement(child)
      }),
    },
  }
}

function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  }
}

여기까지 했으면, 이제 (1) -> (2)를 해볼 차례다.
하지만 아까도 말했다시피, React 조차도 이를 직접적으로 구현하지 않고
babel이라는 빌드 툴의 도움을 받는다.

이를 위해 node 환경에서 이를 트랜스파일링 해보도록 하자

먼저 yarn add --dev @babel/core @babel/cli @babel/plugin-transform-react-jsx
babel pulgin을 설치해주고
npx babel --plugins @babel/plugin-transform-react-jsx index.js --out-file index.js으로
transform할 파일 이름을 넣어 CLI로 실행시켜주면 된다.

근데 지금 상태로 돌리면 React.createElement로 변환되기 때문에
우리가 만든 함수로 하기 위해서는 어노테이션을 붙여줘야한다.

난 내가 만들 React 패키지를 MiniReact라고 하기로 했다.

// utils/react.js
export const createElement = (type, props, ...children) => {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) => 
        typeof child === 'object' 
          ? child 
          : createTextElement(child))),
    },
  };
};

export const createTextElement = (text) => {
  return {
    type: 'TEXT_ELEMENT',
    props: {
      nodeValue: text,
      children: [],
    },
  };
};

export const MiniReact = {
  createElement,
  createTextElement,
};

따라서 MiniReact.createElement를 붙여서 Transform하게 하려면
/** @jsx MiniReact.createElement */ 를 붙여주면 된다.

/** @jsx MiniReact.createElement */

import MiniReact from '../utils/react.js';

window.onload = function () {
  const element = (
    <div className="foo">
      <div>something</div>
      <p>
        Hello <b>World</b>
      </p>
    </div>
  );
};

이 상태에서
npx babel --plugins @babel/plugin-transform-react-jsx index.js --out-file index.js
를 실행하면 다음과 같이 변환된다.

위의 명령어를 나눠보면, 일단 babel를 global로 설치하는건 권장하지 않아 npx를 사용했고,
@babel/plugin-transform-react-jsx 플러그인을 이용해
index.js를 컴파일하고, --out-file index.js은 그 결과를 index.js로 만들어라,
즉 이 경우에는 덮어 씌워라 라는 말이다.

/** @jsx MiniReact.createElement */
import MiniReact from '../utils/react.js';

window.onload = function () {
  const element = MiniReact.createElement(
    'div',
    {
      className: 'foo',
    },
    MiniReact.createElement('div', null, 'something'),
    MiniReact.createElement('p', null, 'Hello ', MiniReact.createElement('b', null, 'World'))
  );
  console.log(element);
};

사실 줄바꿈 이런게 제대로 적용되서 나오진 않았지만, 이정도면 나쁘지 않은 것 같다.

이제 이 element를 render하는 함수를 직접 짜볼 시간이다.

0개의 댓글