목차
3 장

JSX의 본질

JSX가 무엇이고 어떻게 JavaScript 함수 호출로 변환되는지, 그리고 표현식·속성·자식 노드의 규칙을 한 번에 다룹니다.

2장에서 Vite로 첫 프로젝트를 만들면서 자연스럽게 다음과 같은 코드를 봤습니다.

src/App.jsx
function App() {
  return (
    <div>
      <h1>안녕하세요, 리액트!</h1>
    </div>
  );
}

함수 안에서 HTML처럼 생긴 코드를 그냥 return 하고 있습니다. 자바스크립트 안에 HTML이라니, 어딘가 어색하지 않나요? 본 챕터에서는 이 문법 — JSX — 가 정확히 무엇이고 어떻게 동작하는지, 그리고 익혀 두어야 할 기본 규칙을 살펴보겠습니다.

JSX는 무엇인가 #

**JSX (JavaScript XML)**는 자바스크립트에 HTML과 비슷한 마크업 문법을 합쳐 놓은 것입니다. 자바스크립트의 공식 문법은 아니지만, 페이스북이 리액트를 발표할 때 함께 제안한 확장 문법이고, 오늘날 거의 모든 리액트 프로젝트가 JSX를 씁니다.

JSX 없이도 리액트 코드를 작성할 수 있습니다. 다음 두 코드는 정확히 동일한 결과를 만듭니다.

JSX 사용
function App() {
  return <h1>Hello</h1>;
}
JSX 없이
function App() {
  return React.createElement('h1', null, 'Hello');
}

위쪽이 훨씬 읽기 쉽습니다. 그래서 모든 리액트 프로젝트가 JSX를 씁니다.

브라우저는 JSX를 모른다 #

브라우저의 자바스크립트 엔진은 JSX를 직접 이해하지 못합니다. 우리가 JSX로 작성한 코드는 빌드 과정에서 일반 자바스크립트로 변환 된 후 브라우저로 전달됩니다. 2장에서 깐 Vite가 이 변환을 자동으로 처리해 주기 때문에, 우리는 신경 쓰지 않고 JSX를 마음껏 씁니다.

변환의 큰 그림은 다음과 같습니다.

JSX → JS 변환 흐름
src/App.jsx (JSX)
   ↓ Vite (esbuild / SWC)
JS bundle (React.createElement 호출)
   ↓ 브라우저
DOM 요소

8장에서 다룰 key, 14장에서 다룰 reconciliation 모두 이 React.createElement 호출로 만들어진 객체 트리 위에서 동작합니다. 본 챕터에서는 그 윗단의 문법 규칙에 집중하겠습니다.

JSX의 기본 규칙 #

JSX는 HTML과 비슷해 보이지만 몇 가지 다른 규칙이 있습니다. 처음 배울 때 가장 많이 헷갈리는 부분이니 차근차근 짚겠습니다.

규칙 1. 하나의 부모 요소로 감싸야 한다 #

JSX는 결국 함수가 무언가를 return 하는 것이므로, 한 번에 하나의 값만 반환할 수 있습니다. 여러 요소를 한꺼번에 반환하려면 반드시 하나의 부모로 감싸야 합니다.

잘못된 예
function App() {
  return (
    <h1>안녕하세요</h1>
    <p>오늘도 즐거운 하루 보내세요</p>
  );
}
올바른 예
function App() {
  return (
    <div>
      <h1>안녕하세요</h1>
      <p>오늘도 즐거운 하루 보내세요</p>
    </div>
  );
}

<div>로 감싸도 되지만 의미 없는 <div>가 늘어나는 게 마음에 들지 않을 때가 있습니다. 그럴 때는 빈 태그인 Fragment를 씁니다.

Fragment 사용
function App() {
  return (
    <>
      <h1>안녕하세요</h1>
      <p>오늘도 즐거운 하루 보내세요</p>
    </>
  );
}

<>...</>React.Fragment의 짧은 표기입니다. 실제 DOM에는 어떤 요소도 만들지 않으면서 여러 자식을 묶어 주는 역할만 합니다.

규칙 2. 모든 태그는 닫혀야 한다 #

HTML에서는 <img>, <br>, <input> 같은 일부 태그를 닫지 않고 써도 됐습니다. 하지만 JSX에서는 모든 태그를 닫아야 합니다. 자식이 없는 태그는 끝에 /를 붙여 자체 닫음 (self-closing)으로 처리합니다.

JSX
<img src="/cat.png" alt="고양이" />
<br />
<input type="text" />

규칙 3. class 대신 className #

HTML에서 CSS 클래스를 지정할 때 쓰는 class 속성은 자바스크립트의 예약어와 겹치기 때문에 JSX에서는 **className**을 씁니다.

JSX
<div className="card">카드입니다</div>

같은 이유로 <label>for 속성도 htmlFor로 바뀝니다.

JSX
<label htmlFor="email">이메일</label>
<input id="email" type="email" />

규칙 4. 속성 이름은 camelCase #

HTML에서는 속성 이름이 모두 소문자였지만(onclick, tabindex, readonly), JSX에서는 camelCase를 씁니다.

JSX
<button onClick={handleClick} tabIndex={0}>클릭</button>
<input readOnly value="고정값" />

이 규칙은 7장 (이벤트 핸들링)에서 본격적으로 다시 만납니다. onClick, onChange, onSubmit 모두 camelCase입니다.

JSX 안에 자바스크립트 표현식 넣기 #

JSX의 진짜 힘은 마크업 안에 자바스크립트 표현식을 자유롭게 끼워 넣을 수 있다는 점입니다. 중괄호 { } 안에 들어가는 모든 자바스크립트 표현식은 평가되어 그 결과가 화면에 출력됩니다.

src/App.jsx
function App() {
  const name = '철수';
  const age = 30;

  return (
    <div>
      <h1>안녕하세요, {name}!</h1>
      <p>나이: {age}</p>
      <p>10 후에는: {age + 10}</p>
    </div>
  );
}

함수 호출, 삼항 연산자, 배열의 메소드도 모두 표현식이라면 사용할 수 있습니다.

src/App.jsx
function App() {
  const isLoggedIn = true;
  const items = ['사과', '바나나', '체리'];

  return (
    <div>
      <h1>{isLoggedIn ? '환영합니다!' : '로그인 해주세요'}</h1>
      <ul>
        {items.map(item => <li key={item}>{item}</li>)}
      </ul>
    </div>
  );
}
노트
중괄호 안에는 **표현식 (expression)**만 들어갈 수 있습니다. if 문이나 for 루프 같은 **문 (statement)**은 직접 넣을 수 없습니다. 조건부 출력이 필요하면 삼항 연산자나 && 연산자를, 반복 출력이 필요하면 map 메소드를 쓰는 식입니다. 자세한 내용은 7장 조건부 렌더링과 8장 리스트와 key에서 다루겠습니다.

인라인 스타일 #

JSX에서 직접 스타일을 지정하려면 객체 형태로 전달합니다. CSS 속성 이름도 마찬가지로 camelCase입니다.

src/App.jsx
function App() {
  return (
    <h1 style={{ color: 'tomato', fontSize: '32px' }}>
      스타일 적용된 제목
    </h1>
  );
}

중괄호가 두 개인 게 처음에는 어색해 보일 수 있는데, 바깥 { }는 “JSX 안의 자바스크립트 표현식"을 의미하고, 안쪽 { }는 “자바스크립트 객체 리터럴” 이기 때문입니다.

실무에서는 인라인 스타일 대신 CSS Modules / Tailwind / styled-components 같은 도구를 더 많이 씁니다. 이 책은 예제 가독성을 위해 종종 인라인 스타일을 쓰지만, 디자인 시스템 차원의 스타일링은 별도 책에서 다룹니다.

JSX 안의 주석 #

JSX 안에서 주석을 달려면 자바스크립트 표현식이므로 중괄호로 감싸야 합니다.

src/App.jsx
function App() {
  return (
    <div>
      {/* 이것은 JSX 주석입니다 */}
      <h1>제목</h1>
    </div>
  );
}

직접 해보기 #

2장에서 만든 프로젝트의 src/App.jsx를 다음과 같이 바꿔 보세요.

src/App.jsx
function App() {
  const name = '철수';
  const fruits = ['사과', '바나나', '체리'];

  return (
    <>
      <h1>안녕하세요, {name}!</h1>
      <p>오늘의 과일은  {fruits.length}개입니다:</p>
      <ul>
        {fruits.map(fruit => <li key={fruit}>{fruit}</li>)}
      </ul>
    </>
  );
}

export default App;

저장하면 HMR로 즉시 화면이 갱신됩니다. 변수의 값을 바꾸거나 배열에 새 과일을 추가해 보면서, 화면이 어떻게 반응하는지 확인해 보세요.

JSX 코드를 작성하다가 빌드 에러가 나면 십중팔구 위에서 다룬 4가지 규칙 중 하나를 깜빡한 경우입니다. “여러 요소를 부모로 감쌌나?”, “태그를 모두 닫았나?”, “classclassName으로 썼나?”, “속성을 camelCase로 썼나?” 순서로 점검해 보면 대부분 해결됩니다.

연습문제 #

  1. 직접 해보기 코드의 fruits 배열에 본인이 좋아하는 과일 3개를 더 추가하고, 저장 직후 화면에 6개의 항목이 모두 그려지는지 확인해 보세요. 그 뒤 <p>총 N개 부분도 자동으로 6으로 바뀌는지 확인합니다.
  2. <h1>의 스타일을 인라인 스타일로 바꿔서 색상을 tomato로, 글자 크기를 48px로 만들어 보세요. style={{ color: 'tomato', fontSize: '48px' }}처럼 객체 형태로 전달해야 합니다.
  3. fruits 배열을 객체 배열로 바꿔 보세요. 각 항목이 { id, name, emoji } 형태인 객체이고, <li> 안에서는 {fruit.emoji} {fruit.name} 식으로 표시합니다. keyfruit.id를 씁니다. 객체 배열 다루기는 8장 (리스트와 key)의 토대가 됩니다.

한 줄 요약: JSX는 자바스크립트 함수 호출 (React.createElement)로 변환되는 문법이다. 부모로 감싸기 / 태그 닫기 / className · htmlFor / camelCase 속성 — 4가지 규칙만 익히면 된다. 중괄호 { }로 JSX 안에 자바스크립트 표현식을 자유롭게 끼워 넣을 수 있다.

다음 챕터 #

다음 4장 컴포넌트와 props에서는 위 예제의 App 같은 함수가 사실 리액트의 가장 중요한 단위인 컴포넌트임을 짚고, 컴포넌트끼리 데이터를 주고받는 통로인 props를 살펴봅니다. 17장 (props와 children 타이핑)에서 TypeScript로 그 인터페이스를 굳히는 작업의 토대가 됩니다.

X