JSX의 실체

Tekiter·2021년 12월 10일
5
post-thumbnail

이런 의문 든 적 없나요?

  • 어째서 JSX는 HTML 문법을 JS 안에서 쓸 수 있는거지??
  • 그러면 JSX 값은 자바스크립트에선 뭐지??
  • 컴포넌트 이름은 왜 대문자로 시작해야 하지??
  • 왜 CSS 클래스 줄 때 class=""하면 안되고 className="" 해야하지??
  • html 에서는 <button tabindex="1"></button> 인데 왜 JSX에서는 tabIndex 로 써야하지??
  • 컴포넌트에서 JSX 리턴할때 왜 꼭 상위 태그가 하나여야 하지??

JSX에 대해 자세히 알아봅시다.


사실 리액트에서 JSX는 필수가 아닙니다!!

JSX 없이 사용하는 React - React

리액트 함수형 컴포넌트가 될 조건은 다음과 같습니다.

  1. 함수 이름이 대문자로 시작해야 한다.
  2. 함수의 인자는 최대 하나여야 하고 그 인자는 props 객체가 된다.
  3. 리턴값으로 React element 를 반환한다.

3번의 리턴해야 하는 것이 JSX 가 아니라는 것에 주목해봅시다. 그럼 React Element 가 뭘까요?

React Element 란?

리액트는 일반적으로 DOM 을 직접 조작하지 않습니다. 많이들 알고 계신 것처럼 리액트는 Virtual DOM을 만들고 실제 DOM 과 비교하여 변경된 부분만 업데이트 합니다.

여기서 React element 가 모여서 Virtual DOM을 만들게 됩니다! State 변경이 일어날 때 마다 새로운 변경된 값들을 넣어서 컴포넌트 함수들을 연쇄적으로 호출하게 됩니다.

연쇄적으로 호출한다는 건 무슨 뜻일까요? 다음과 같이 App 컴포넌트에서 Header 컴포넌트를 사용한다고 해 봅시다.

function App() {

  return (
    <div>
      <Header />
    </div>
  );
}

function Header(props) {
  return <h1>{props.header}</h1>;
}

뭔가 상태가 변경되면 (물론 여기에선 상태가 없지만 그런 상황이라 가정합시다) 리액트는 App 컴포넌트 함수를 호출해서 해당 컴포넌트가 반환하는 React Element를 가져오려고 합니다.

그런데 여기서 App이 리턴하는 곳을 보면 Header를 사용하고 있으므로, Header 컴포넌트 함수도 호출이 됩니다. 만약 Header에서도 다른 컴포넌트를 사용하고 있다면 그 컴포넌트 함수도 호출되고, 또 그 안에서도 다른게 호출되고... 하면서 모든 사용하는 컴포넌트에 대해 컴포넌트 함수들이 연쇄적으로 호출됩니다.

React Element 사용

React element는 React 모듈의 React.createElement() 함수로 만들 수 있습니다! 이 함수의 정의는 다음과 같습니다.

import React from 'react';

React.createElement(component, props, ...children)

/*
component => 컴포넌트 함수 또는 HTML 태그 이름 문자열
props => 컴포넌트가 받을 props
children => <div><p></p><button></button></div> 에서 p와 button은 div의 children이다.
*/

만약 component 인자에 문자열이 들어오면 createElement 함수는 이를 해당 이름의 html 태그를 만드는 것으로 인식합니다. 그리고 이때 props는 만들어진 해당 DOM 객체의 속성으로 들어갑니다.

이걸 이용해서 컴포넌트를 하나 만들어볼까요?

function InputComponent() {
  return React.createElement("div", { className: "input-control dark" },
    React.createElement("input", { type: "text" })
  );
}

이 컴포넌트를 사용하면 화면에는 다음과 같이 렌더링됩니다.

<div class="input-control dark">
    <input type="text">
</div>

className이 class로 들어갔죠? 이건 리액트가 실제 DOM을 만들 때 실제 html 태그 문자열을 만들어 넣는 게 아니라 document.createElement() 함수를 호출해서 DOM Element를 만들고 여기에 prop을 적용해서 넣기 때문인데요,

const divElement = document.createElement('div');
divElement.className = "hello world";

다음과 같이 DOM에서 CSS 클래스를 넣을 땐 .class 가 아니라 .className 속성에 넣어줘야 합니다. 마찬가지로 tabindex 같은 속성도 DOM에서는 .tabIndex 로 접근해야 하기 때문에 I 를 대문자로 써줘야 합니다.

그렇다면 이벤트는 어떨까요?? onclick 은 왜 onClick 이죠??

이벤트들은 리액트에서 조금 다르게 처리됩니다.
합성 이벤트(SyntheticEvent) - React, 이벤트 처리하기 - React

컴포넌트에 다른 컴포넌트를 넣으려면 문자열 대신 컴포넌트 함수 이름을 넣어주면 됩니다!

function InputComponent() {
  return React.createElement("div", { className: "input-control" },
    React.createElement(TextInput, null) // 👈 여기!
  );
}

function TextInput() {
  return React.createElement("input", { type: "text" });
}

이렇게 컴포넌트는 React.createElement() 함수로 React Element를 만들어 반환하면 만들 수 있습니다!

그런데 이 방식을 그대로 사용하자니 가독성이 정말 좋지 않습니다.

function TopBar(props) {
  return React.createElement(
    "div",
    null,
    React.createElement(Header, {
      username: props,
    }),
    React.createElement(Navs, {
      size: "small",
    })
  );
}

function Header(props) {
  return React.createElement(
    "h1",
    null,
    "환영합니다! ",
    props.username,
    " 님!"
  );
}

function Navs(props) {
  return React.createElement(
    "div",
    {
      className: props.size === "small" ? "nav-small" : "",
    },
    React.createElement("button", null, "Home"),
    React.createElement("button", null, "Profile")
  );
}

JSX가 이 문제를 해결해줍니다.

JSX

JSX는 자바스크립트의 문법적 설탕(Syntax Sugar)으로 자바스크립트 표준이 아닙니다!

JSX는 기본적으로 실행되기 전에 올바른 자바스크립트 문법으로 변환되어야 합니다. 우리가 사용하는 JSX가 어떻게 바뀌는지 볼까요?

function Header(props) {
  return <h1 className="header">Hello {props.username} world!</h1>;
}

/*****************************************/

function Header(props) {
  return React.createElement("h1", { className: "header" },
    "Hello ", props.username, " world!"
  );
}

리액트에서 JSX는 React.createElement() 형태로 변환됩니다! 여기서 JSX의 props는 그대로 React.createElement() 의 props로 들어가게 됩니다. 따라서 CSS 클래스 같은 경우엔 className 으로 써야 합니다. tabIndex 도 마찬가지입니다.

컴포넌트 함수는 어떻게 될까요?

function TopBar() {
	return <div><Header username="Tekiter"/></div>;
}

function Header(props) {
  return <h1>Hello{props.username}!</h1>;
}

/*****************************************/

function TopBar() {
  return React.createElement("div", null,
    React.createElement(Header, { username: "Tekiter" })
  );
}

function Header(props) {
  return React.createElement("h1", null,
    "Hello", props.username, "!"
  );
}

이렇게 컴포넌트 함수 이름이 들어갑니다!

여기서 가만 보면 JSX에서 태그 이름에 따라서 변환 동작이 달라야 합니다. DOM Element 여야 한다면 태그 이름 문자열로 변환되어야 하고, 컴포넌트 함수여야 한다면 문자열이 아닌 함수 이름이 들어가야 합니다.

JSX는 이 경우를 태그 이름 첫글자가 대문자인지 소문자인지로 구별합니다. 대문자라면 컴포넌트 함수, 소문자라면 DOM Element로 여깁니다.
이게 바로 리액트 컴포넌트 이름이 대문자로 시작해야 하는 이유입니다.

이제 마지막 의문인 컴포넌트에서 JSX 리턴할때 왜 꼭 상위 태그가 하나여야 하지?? 도 추론 가능합니다.

JSX 지원

JSX는 자바스크립트 표준이 아니기 때문에 이를 바로 해석해주는 브라우저는 없습니다. 그렇기 때문에 다른 변환 프로그램의 도움을 얻어야 합니다. JSX를 변환해주는 기능을 가지고 있는 변환기에는 Babel 타입스크립트 tsc 등이 있습니다.

Create React App 의 설정에서는 Babel 을 사용해 JSX를 변환합니다. Babel은 JSX 말고도 다양한 변환 작업을 수행하므로 한번 찾아보는 것을 추천합니다!

Babel이 변환한 코드가 어떻게 되는지 보고 싶으면 Babel 홈페이지에서 체험해 볼 수 있습니다.

Babel · The compiler for next generation JavaScript

더 자세하고 정확한 설명

JSX 소개 - React
JSX 이해하기 - React

0개의 댓글