본문 바로가기
FRONT-END/React

나만의 React 만들기(Build your own react) - STEP 1

by 랄라J 2024. 9. 4.

https://pomb.us/build-your-own-react/ 을 기반으로 학습 내용을 정리한 글입니다.


STEP 1 - The createElement Function

이제는 React 코드를 우리만의 React인 Didact로 대체해보자.
우선 createElement function을 만들어보는 것이 이번 step의 목표다.

 

JSX를 JS로 변환하는 createElement 함수를 만들어보자.

const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
)
const container = document.getElementById("root")
ReactDOM.render(element, container)

 

우선 STEP 0을 다시 되짚어보면,
위 코드 element JSX가 babel에 의해 변환이 되면 아래와 같이 변환된다고 했다!

const element = React.createElement(
  "div",
  { id: "foo" },
  React.createElement("a", null, "bar"),
  React.createElement("b")
);

 

위 createElement 함수가 실행되면  아래와 같은 객체가 출력되는데

{
  type: "div",
  props: {
    id: "foo",
    children: [
      {
        type: "a",
        props: {
          children: "bar",
        },
      },
      {
        type: "b",
        props: {
          children: [],
        },
      },
    ],
  },
}

 

위 객체와 같이 변환되기 위해서 createElement 함수는 아래와 같은 형태가 될 것이다.

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

props에는 spread 연산자를 사용하고, children에는 rest parameter 구문을 사용한다.
props는 객체로 전달되며, ...props는 객체의 모든 키-값 쌍을 복사해서 새로운 객체로 만든다.
children의 값은 나머지 인자로 전달된 요소들이 배열 형태로 들어간다.

 

조금 더 코드를 예시로 살펴보자.

createElement("div");

아래 코드 처럼 type만 있는 경우에는 createElement가 호출되면 반환되는 객체는 아래 형태가 된다.

{
  "type": "div",
  "props": { "children": [] }
}

 

 

createElement("div", { id: "myDiv", className: "container" }, "Hello", "World");

type, 여러 props와 children이 들어오는 경우에는 createElement가 호출되면 반환되는 객체는 아래 형태가 된다.

{
  type: 'div',
  props: {
    id: 'myDiv',
    className: 'container',
    children: ['Hello', 'World']
  }
}

 

createElement 함수가 children 요소를 어떻게 처리하는지 알아보자.

children: children.map((child) =>
  typeof child === "object" ? child : createTextElement(child)
);

children은 배열이다. 이 배열안에는 div 안에 text 같은 자식들이 들어있다. map 함수는 배열의 각 요소를 순회하며 배열을 생성하고, 각 child 요소에 대해 type이 object인지 확인한다.

child가 객체라면 그대로 반환한다.
하지만 child가 객체가 아니라면 createTextElement(child) 함수를 호출해 이를 또 한번 래핑한다.

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

 

이 함수를 통해서 객체가 아닌 child 객체도 객체로 만들어 버리는 것이다.
createTextElement 함수는 text를 받아 TEXT_ELEMENT라는 타입의 객체를 반환한다.
createTextElement는 text를 받아 nodeValue 속성에 텍스트 값을 저장하고, children 배열은 비어있다.
children 배열이 비어있는 이유는 텍스트 노드는 자식 요소를 가질 수 없기 때문이다.

 

createElement("div", null, "Hello", "World", 123);

위 코드가 호출되면 children은 ["Hello", "World", 123]가 된다.
이 3가지 모두 객체가 아니므로 createTextElement 함수로 처리된다.
최종적으로 children 배열은 아래 코드와 같이 변환된다.

[
  { type: "TEXT_ELEMENT", props: { nodeValue: "Hello", children: [] } },
  { type: "TEXT_ELEMENT", props: { nodeValue: "World", children: [] } },
  { type: "TEXT_ELEMENT", props: { nodeValue: "123", children: [] } },
];

 

하나 구분해서 알아두어야 할 것은 실제 React에서는 위 createTextElement 함수로 래핑하는 것처럼 원시값인 경우 자동으로 래핑하거나 빈 배열을 생성하진 않는다는 것이다. 지금은  성능보다는 코드의 단순함을 우선시하기 때문에, 모든 child가 객체로 일관되게 처리되도록 하는 것이 코드의 일관성을 유지하기 쉽기 때문이다.

 

이제 React의 createElement를 Didact를 정의하고 Didact의 createElement를 사용하도록 코드를 수정하자

const Didact = {
  createElement,
};

const element = Didact.createElement(
  "div",
  { id: "foo" },
  Didact.createElement("a", null, "bar"),
  Didact.createElement("b")
);

 

Didact에서도 JSX를 사용하고 싶기 때문에, babel에게 React 대신 Didact의 createElement를 사용하도록 지시하는 아래 방법을 따라야 한다.

/** @jsx Didact.createElement */
const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
);

위와 같은 주석을 사용하면 babel이 JSX를 transpile 할 때 우리가 정의한 함수를 사용하게 될 것이다.

 

즉, STEP 1을 완료한 상태의 전체 코드는 아래와 같다.
아직 render를 하는 코드가 없어 화면에 표시되지 않는 상황이다.
다음 STEP 2를 진행하며 더 알아보자~!

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: [],
    },
  };
}

const Didact = {
  createElement,
};

/** @jsx Didact.createElement */
const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
);

const container = document.getElementById("root");

여기까지 STEP 1 완료!

728x90

댓글