Logo

[React] JSX와 Babel

리액트(React)는 보통 HTML과 매우 흡사한 JSX 코드로 작성하는 경우가 대부분인데요. 이 JSX 때문에 리액트를 좋아하는 사람도 많지만 반면에 이 JSX 때문에 리액트를 싫어한다는 분들도 많이 보게 됩니다.

이번 포스팅에서는 이렇게 말도 많고 탈도 많은 리액트의 JSX와 JSX를 변환해주는 Babel에 대해서 알아보도록 하겠습니다.

왜 React를 임포트할까?

리액트를 처음 사용해 본 자바스크립트 개발자라면 한 번 쯤 이러한 생각을 해보신 적이 있으실 겁니다.

내 코드에 React를 사용하는 곳이 없는데 왜 React를 굳이 임포트해야 할까?

import React from "react";

function Box({ id, children }) {
  return <div id={id}>{children}</div>;
}

React를 임포트해야하는 이유는 바로 이 JSX 코드는 사실 아래와 같이 변환이 되기 때문입니다.

import React from "react";

function Box({ id, children }) {
  return React.createElement(
    "div",
    {
      id: id,
    },
    children
  );
}

React.createElement(...) 부분이 보이시죠? 😉

브라우저에서 JSX 코드를 바로 실행한다면?

우리가 리액트를 사용하는 프로젝트에서는 위와 같이 JSX 코드가 자바스크립트로 변환되는 것이 당연하게 여겨집니다. 일반적으로 프로젝트 초기에 이미 다른 누군가에 의해 셋업이 되었거나, Create React App과 같은 프레임워크들이 이 부분에 대해 걱정할 필요없이 셋업이 되서 나오기 때문입니다.

본 포스팅에서는 이 숨겨진 과정을 이해하기 위해서 JSX 코드를 아무런 사전 세팅없이 바로 브라우저에서 한 번 실행을 해보겠습니다.

브라우저 실행 환경에서는 npm 패키지를 바로 불러올 수 없으므로, HTML의 <script> 태그를 이용하여 React와 React DOM 패키지를 CDN 주소를 통해 불러옵니다. 그 다음, 위와 동일하게 <Box/> 컴포넌트를 작성하고, 이 컴포넌트로 리액트 엘리먼트(reactElement)를 하나 생성한 후, ReactDOM.render() 함수에 넘겨 화면에 랜더링합니다.

<body>
  <div id="root"></div>

  <script src="https://unpkg.com/react@16.14.0/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16.14.0/umd/react-dom.development.js"></script>

  <script type="module">
    function Box({ id, children }) {
      return <div id={id}>{children}</div>
    }
    const reactElement = <Box id="box1">박스</Box>
    ReactDOM.render(reactElement, document.getElementById("root"))
  </script>
</body>

위 코드를 브라우저에서 바로 실행해보면 브라우저 콘솔에 다음과 같이 문법 오류가 발생할 것입니다. 이를 통해 우리는 브라우저가 JSX 코드를 바로 실행할 수 없다는 것을 알 수 있습니다.

index.html:38 Uncaught SyntaxError: Unexpected token '<'

JSX 코드를 자바스크립트로 변환하기 (Babel)

JSX 코드는 자바스크립트가 확장된 형태로 엄밀한 얘기해서 브라우저가 바로 실행할 수 있는 자바스크립트 코드가 아닙니다. 이렇게 JSX와 같이 자바스크립트의 표준에서 벗어난 코드는 소위 트랜스파일(transpile)이라는 과정을 거쳐서 브라우저가 이해할 수 있는 순수한 자바스크립트 코드로 변환을 해줘야합니다.

Babel 도구와 transpile 개념에 대한 전반적인 설명은 관련 포스팅를 참고 바라겠습니다.

이렇게 JSX 코드를 자바스크립트로 변환하는 과정은 브라우저에서도 진행해볼 수 있습니다.

위에서 작성한 코드에서 Babel 패키지를 불러오는 <script> 태그를 추가하고, <script type="module"> 부분을 <script type="text/babel">로 바꿔줍니다.

<body>
  <div id="root"></div>

  <script src="https://unpkg.com/react@16.14.0/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16.14.0/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone@7.12.3/babel.js"></script>

  <script type="text/babel">
    function Box({ id, children }) {
      return <div id={id}>{children}</div>
    }
    const reactElement = <Box id="box1">박스</Box>
    ReactDOM.render(reactElement, document.getElementById("root"))
  </script>
</body>

이렇게 코드를 수정한 후, 브라우저를 새로 고침하면 화면에 원했던 박스라는 문구가 나타날 것입니다. 또한, 이렇게 JSX 코드를 변환하는 것은 상용 환경에서는 추천되지 않는 방법이라는 경고 메시지가 브라우저 콘솔에 나타날 것입니다.

"You are using the in-browser Babel transformer. Be sure to precompile your scripts for production - https://babeljs.io/docs/setup/"

브라우저의 개발자 도구에서 <head> 영역을 살펴보면, Babel이 변환해준 자바스크립트 코드를 찾을 수 있으실 겁니다.

<head>
  <script>
    "use strict"

    function Box(_ref) {
      var id = _ref.id,
        children = _ref.children
      return React.createElement(
        "div",
        {
          id: id,
        },
        children
      )
    }

    var reactElement = React.createElement(
      Box,
      {
        id: "box1",
      },
      "\uBC15\uC2A4"
    )
    ReactDOM.render(reactElement, document.getElementById("root"))
  </script>
</head>

전체 코드는 아래에서 확인해보실 수 있습니다.

React.createElement()

지금까지 JSX 코드는 결국 Babel을 이용하여 React.createElement(...)로 변환을 해줘야 브라우저에서 실행이 가능하다는 것을 배웠습니다. 만약에 Babel이 변환해준 자바스크립트를 코드를 머리로 상상할 수 있다면 리액트를 사용할 때 좀 더 나은 코드를 작성하는데 큰 도움이 됩니다.

예를 들어, 다음과 같이 HTML의 <button> 엘리먼트로 이뤄진 간단한 JSX 코드는 아래와 같이 변환이 됩니다.

const simpleButton = (
  <button type="submit" onClick={() => alert("클릭")}>
    제출
  </button>
);
const simpleButton = React.createElement(
  "button",
  {
    type: "submit",
    onClick: () => alert("클릭"),
  },
  "\uC81C\uCD9C"
);

createElement() 메서드의 첫번째 인자로 “button”이 넘어가고, 두번째 인자로 props가, 마지막 인자로 자식(children)이 넘어갑니다. 즉, ReactcreateElement() 메서드는 다음과 같은 구조를 가집니다.

function createElement(elementType, props, ...children) {}

이번에는 좀 더 복잡한 예제로 SubmitButton 컴포넌트를 작성하고, 이 컴포넌트를 이용하여 좀 더 복잡한 JSX 코드를 작성해보겠습니다.

function SubmitButton({ children, onClick, disabled }) {
  return (
    <button type="submit" onClick={onclick} disabled={disabled}>
      {disabled ? "처리 중" : "제출"}
    </button>
  );
}

const complexButton = (
  <SubmitButton onClick={() => alert("클릭")} disabled={false} />
);

const complexButtons = (
  <>
    <SubmitButton onClick={() => alert("클릭")} /> <SubmitButton disabled />
  </>
);
function SubmitButton({ children, onClick, disabled }) {
  return React.createElement(
    "button",
    {
      type: "submit",
      onClick: onclick,
      disabled: disabled,
    },
    disabled ? "처리 중" : "제출"
  );
}

const complexButton = React.createElement(SubmitButton, {
  onClick: () => alert("클릭"),
  disabled: false,
});

const complexButtons = React.createElement(
  React.Fragment,
  null,
  React.createElement(SubmitButton, {
    onClick: () => alert("클릭"),
  }),
  " ",
  React.createElement(SubmitButton, {
    disabled: true,
  })
);

complexButton를 통해 createElement() 메서드의 첫번째 인자로 리액트의 함수 컴포넌트도 넘길 수 있으며, 마지막 인자로 자바스크립트 표현식(expression)도 넘길 수 있다는 것을 알 수 있습니다. complexButtons를 통해 createElement() 메서드의 마지막 파라미터(children)는 가변 인자이기 때문에 여러 개의 문자열과 리액트 앨리먼트를 혼합해서 넘길 수 있다는 것도 알 수 있습니다.

마치면서

이상으로 JSX 코드가 Babel에 의해서 어떻게 자바스크립트로 변환되어 브라우저에서 실행되는지 살펴보았습니다. Babel의 Try it out 페이지에서 본인이 작성한 JSX 코드를 입력하고 어떻게 자바스크립트로 변환이 되는지 직접 확인해보시는 것을 추천드립니다. 이 글을 통해 JSX 없이 날 것의 React API에 대해서 좀 더 관심이 가지게 되셨다면 아래 포스팅를 통해 관련 내용을 참고바랍니다.