본문 바로가기

JSX 문법

JSX의 단일 루트·속성 이름·태그 닫기 규칙을 익히고 중괄호로 JavaScript 표현식을 UI에 삽입합니다.

1장 마지막에서 잠시 언급했듯이, 리액트 컴포넌트를 작성할 때 우리는 HTML과 매우 유사하게 생긴 특별한 문법을 사용했습니다.

바로 JSX(JavaScript XML)입니다.

앞 절의 첫 React 앱을 실행한 뒤, 태그를 묶는 구조와 그 안에서 계산하는 값을 나누어 읽습니다. JSX는 React에서 널리 쓰는 작성 방식이며, React element를 만드는 다른 JavaScript API도 있습니다.

JSX는 익숙해지면 리액트 컴포넌트의 UI 구조를 직관적으로 작성하는 데 도움이 됩니다.

JSX에서 element 트리까지 — 상태 추적

변환 시 소스와 JavaScript, 실행 시 현재 입력과 element 값은 시점과 값이 다르다. JSX/현재 입력/UI 설명을 고정 역할로 보강하되 element를 DOM으로 표시하지 않는다.

JSX는 JS 코드로 변환되고 실행될 때 현재 값을 읽습니다

JSX: TRANSFORM & EXECUTE

JSX는 JS 코드로 변환되고 실행될 때 현재 값을 읽습니다

태그를 작성하는 시점과 name·price 같은 값이 평가되는 시점을 구분합니다. React element는 화면의 구조를 기술하는 값이며, 이후 React가 렌더 결과를 DOM에 반영합니다.

JSX 변환과 실행 시 값 평가JSX소스→JavaScript변환→현재입력으로컴포넌트실행→Reactelement의4노드3간선이다. 도구의변환과렌더계산의실행시점이다르며element는DOM노드자체가아니다.
JSX 소스태그 · props · 표현식
연결 관계JSX 소스 · 태그 · props · 표현식 → JavaScript 변환 · 개발 · 빌드 도구
JavaScript 변환개발 · 빌드 도구
연결 관계JavaScript 변환 · 개발 · 빌드 도구 → 컴포넌트 실행 · 현재 입력 평가
컴포넌트 실행현재 입력 평가
연결 관계컴포넌트 실행 · 현재 입력 평가 → React element · UI를 기술하는 값
React elementUI를 기술하는 값
JSX 변환과 실행 시 값 평가 · 모바일JSX소스→JavaScript변환→현재입력으로컴포넌트실행→Reactelement의4노드3간선이다. 도구의변환과렌더계산의실행시점이다르며element는DOM노드자체가아니다. 모바일에서도 노드와 연결 방향·독립 경로가 같다.
JSX 소스태그 · props · 표현식
연결 관계JSX 소스 · 태그 · props · 표현식 → JavaScript 변환 · 개발 · 빌드 도구
JavaScript 변환개발 · 빌드 도구
연결 관계JavaScript 변환 · 개발 · 빌드 도구 → 컴포넌트 실행 · 현재 입력 평가
컴포넌트 실행현재 입력 평가
연결 관계컴포넌트 실행 · 현재 입력 평가 → React element · UI를 기술하는 값
React elementUI를 기술하는 값

1. 소스 작성과 변환

<h2>{name}</h2>
<p>가격: {price}원</p>

두 태그는 부모 요소나 Fragment 안에 작성합니다. 도구가 JSX를 JS 호출로 바꾸며, 사용한 JSX runtime 설정에 따라 호출 형태는 달라집니다.

2. 실행 시 현재 props 평가

name: "키보드"
price: 30000
→ 제목 "키보드", 가격 30000원

ProductCard가 호출되면 name과 price를 읽고 현재 UI를 기술하는 React element 트리를 반환합니다.

문법 점검: 구조와 태그

인접 JSX는 묶고 태그는 닫습니다. Fragment는 추가 DOM 노드를 만들지 않습니다.

문법 점검: 속성과 표현식

className·htmlFor·onClick을 사용하고, 중괄호 안에는 JS 표현식을 넣습니다. aria-*·data-* 속성은 하이픈을 유지합니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분내용
전체 관계·조건 설명JSX 소스→JavaScript 변환→현재 입력으로 컴포넌트 실행→React element의 노드 4개·간선 3개다. 도구의 변환과 렌더 계산의 실행 시점이 다르며 element는 DOM 노드 자체가 아니다.
전체 관계·조건 설명JSX 소스→JavaScript 변환→현재 입력으로 컴포넌트 실행→React element의 노드 4개·간선 3개다. 도구의 변환과 렌더 계산의 실행 시점이 다르며 element는 DOM 노드 자체가 아니다. 모바일에서도 노드와 연결 방향·독립 경로가 같다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
JSX 작성source: JSX / 현재 값: 아직 실행 전태그/props/표현식이 변환 입력에 있다JSX는 실제 HTML 문자열을 바로 표시하는 것이 아니다
JavaScript 변환산출: React element 생성 코드 / DOM: 아직 없음도구가 실행 가능한 JS로 변환한다변환 시점과 컴포넌트 입력 평가 시점은 다르다
현재 입력 평가입력: 현재 props/state / 결과: element 값컴포넌트가 현재 값을 읽어 element UI 설명을 반환한다element는 DOM 노드 자체가 아니며 commit은 별도다

JSX에서 element 트리까지 — 상태 추적

변환 시 소스와 JavaScript, 실행 시 현재 입력과 element 값은 시점과 값이 다르다. JSX/현재 입력/UI 설명을 고정 역할로 보강하되 element를 DOM으로 표시하지 않는다.

JSX는 JS 코드로 변환되고 실행될 때 현재 값을 읽습니다

JSX: TRANSFORM & EXECUTE

JSX는 JS 코드로 변환되고 실행될 때 현재 값을 읽습니다

태그를 작성하는 시점과 name·price 같은 값이 평가되는 시점을 구분합니다. React element는 화면의 구조를 기술하는 값이며, 이후 React가 렌더 결과를 DOM에 반영합니다.

JSX 변환과 실행 시 값 평가JSX소스→JavaScript변환→현재입력으로컴포넌트실행→Reactelement의4노드3간선이다. 도구의변환과렌더계산의실행시점이다르며element는DOM노드자체가아니다.
JSX 소스태그 · props · 표현식
연결 관계JSX 소스 · 태그 · props · 표현식 → JavaScript 변환 · 개발 · 빌드 도구
JavaScript 변환개발 · 빌드 도구
연결 관계JavaScript 변환 · 개발 · 빌드 도구 → 컴포넌트 실행 · 현재 입력 평가
컴포넌트 실행현재 입력 평가
연결 관계컴포넌트 실행 · 현재 입력 평가 → React element · UI를 기술하는 값
React elementUI를 기술하는 값
JSX 변환과 실행 시 값 평가 · 모바일JSX소스→JavaScript변환→현재입력으로컴포넌트실행→Reactelement의4노드3간선이다. 도구의변환과렌더계산의실행시점이다르며element는DOM노드자체가아니다. 모바일에서도 노드와 연결 방향·독립 경로가 같다.
JSX 소스태그 · props · 표현식
연결 관계JSX 소스 · 태그 · props · 표현식 → JavaScript 변환 · 개발 · 빌드 도구
JavaScript 변환개발 · 빌드 도구
연결 관계JavaScript 변환 · 개발 · 빌드 도구 → 컴포넌트 실행 · 현재 입력 평가
컴포넌트 실행현재 입력 평가
연결 관계컴포넌트 실행 · 현재 입력 평가 → React element · UI를 기술하는 값
React elementUI를 기술하는 값

1. 소스 작성과 변환

<h2>{name}</h2>
<p>가격: {price}원</p>

두 태그는 부모 요소나 Fragment 안에 작성합니다. 도구가 JSX를 JS 호출로 바꾸며, 사용한 JSX runtime 설정에 따라 호출 형태는 달라집니다.

2. 실행 시 현재 props 평가

name: "키보드"
price: 30000
→ 제목 "키보드", 가격 30000원

ProductCard가 호출되면 name과 price를 읽고 현재 UI를 기술하는 React element 트리를 반환합니다.

문법 점검: 구조와 태그

인접 JSX는 묶고 태그는 닫습니다. Fragment는 추가 DOM 노드를 만들지 않습니다.

문법 점검: 속성과 표현식

className·htmlFor·onClick을 사용하고, 중괄호 안에는 JS 표현식을 넣습니다. aria-*·data-* 속성은 하이픈을 유지합니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.

관계도의 보충 설명

원도표의 전체 관계·조건·설명
구분내용
전체 관계·조건 설명JSX 소스→JavaScript 변환→현재 입력으로 컴포넌트 실행→React element의 노드 4개·간선 3개다. 도구의 변환과 렌더 계산의 실행 시점이 다르며 element는 DOM 노드 자체가 아니다.
전체 관계·조건 설명JSX 소스→JavaScript 변환→현재 입력으로 컴포넌트 실행→React element의 노드 4개·간선 3개다. 도구의 변환과 렌더 계산의 실행 시점이 다르며 element는 DOM 노드 자체가 아니다. 모바일에서도 노드와 연결 방향·독립 경로가 같다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
JSX 작성source: JSX / 현재 값: 아직 실행 전태그/props/표현식이 변환 입력에 있다JSX는 실제 HTML 문자열을 바로 표시하는 것이 아니다
JavaScript 변환산출: React element 생성 코드 / DOM: 아직 없음도구가 실행 가능한 JS로 변환한다변환 시점과 컴포넌트 입력 평가 시점은 다르다
현재 입력 평가입력: 현재 props/state / 결과: element 값컴포넌트가 현재 값을 읽어 element UI 설명을 반환한다element는 DOM 노드 자체가 아니며 commit은 별도다

JSX란 무엇인가?

먼저 JSX가 태그 구조, 중괄호 표현식, 속성 이름 규칙으로 나뉘어 읽힌다는 점을 한 화면에서 잡고 들어갑니다.

JSX는 JavaScript XML의 약자로, 이름에서 알 수 있듯이 자바스크립트 코드 내부에 XML/HTML과 유사한 구문을 작성할 수 있게 해주는 문법 확장입니다.

리액트에서 UI를 기술할 때 사용합니다. 개발 도구는 JSX를 JavaScript 호출로 변환하고, 컴포넌트가 실행될 때 그 호출이 현재 값으로 React element를 만듭니다. 이 값은 화면의 구조를 기술하며, 그 자체가 브라우저 DOM 노드는 아닙니다.

JSX의 특징
  • HTML처럼 보이지만 자바스크립트입니다. JSX는 겉보기에는 HTML 태그처럼 보이지만, 실제로는 자바스크립트 코드의 일부입니다. 태그 사이에 JavaScript 표현식을 넣고, 복잡한 조건·반복은 함수의 다른 부분에서 계산해 가져올 수 있습니다.
  • 컴파일 과정: 웹 브라우저는 JSX를 직접 이해하지 못합니다. 따라서 JSX 코드는 바벨(Babel)과 같은 트랜스파일러(Transpiler)에 의해 일반 자바스크립트 코드로 변환된 후 브라우저에서 실행됩니다. Vite 기반 프로젝트에서는 이 변환 과정이 기본 개발 서버/빌드 파이프라인에 포함되어 있습니다.
  • UI를 선언적으로 표현: JSX는 무엇을 보여줄 것인가를 직관적으로 선언할 수 있게 하여 UI 구조를 한눈에 파악하기 용이하게 만듭니다.

JSX의 기본 규칙

JSX는 HTML과 유사하지만 몇 가지 중요한 규칙을 따릅니다.

이 규칙들을 정확히 이해하는 것이 중요합니다.

항상 하나의 루트(Root) 요소 반환

여러 JSX 태그를 한 표현식으로 반환하려면 부모 요소나 Fragment로 묶습니다. 배열 등 다른 React node를 반환하는 방법과는 구분합니다.

위 제한은 하나의 JSX 표현식 안에서 인접 태그를 묶어야 한다는 뜻입니다. 화면에 두 요소를 나란히 배치하지 못한다는 뜻은 아닙니다.

// ⭕ 올바른 예시: 하나의 div로 감싸져 있음
function MyComponent() {
  return (
    <div>
      <h1>안녕하세요!</h1>
      <p>리액트를 배워봅시다.</p>
    </div>
  );
}

// ❌ 잘못된 예시: 두 개의 최상위 요소
// function MyComponent() {
//   return (
//     <h1>안녕하세요!</h1>
//     <p>리액트를 배워봅시다.</p>
//   );
// }

만약 불필요한 <div> 태그를 추가하고 싶지 않다면, 프래그먼트(Fragment)를 사용할 수 있습니다.

프래그먼트는 <></> 또는 <Fragment></Fragment>와 같이 작성하며, 실제 DOM에는 렌더링되지 않는 묶음 역할만 합니다.

// ⭕ 올바른 예시: 프래그먼트 사용
import { Fragment } from 'react'; // 명시형 Fragment를 쓸 때만 가져옵니다.

function MyComponent() {
  return (
    <> {/* 단축 문법: <Fragment>와 같은 묶음 역할 */}
      <h1>안녕하세요!</h1>
      <p>리액트를 배워봅시다.</p>
    </>
  );
}

// 또는 명시적으로
function MyComponentWithFragment() {
  return (
    <Fragment>
      <h1>안녕하세요!</h1>
      <p>리액트를 배워봅시다.</p>
    </Fragment>
  );
}

프래그먼트는 특히 여러 컴포넌트를 묶어 반환하거나, 리스트를 렌더링할 때 유용하게 사용됩니다.

HTML 속성 이름의 CamelCase 규칙

JSX에서는 HTML 속성(Attribute) 이름을 작성할 때 자바스크립트의 CamelCase(카멜 케이스) 규칙을 따릅니다.

React DOM에 전달하는 프로퍼티 이름을 사용하기 때문입니다. 모든 HTML 속성을 기계적으로 변환하지는 않습니다. aria-label, data-testid처럼 aria-*·data-* 이름은 하이픈을 유지합니다.

  • class -> className
  • for -> htmlFor
  • tabindex -> tabIndex
  • onclick -> onClick

예시

function MyButton() {
  return (
    <button className="my-button" onClick={() => console.log('클릭!')}>
      클릭해주세요
    </button>
  );
}

// HTML에서는 다음과 같았겠죠?
// <button class="my-button" onclick="console.log('클릭!')">
//   클릭해주세요
// </button>

자식 요소가 없는 태그는 항상 닫기

HTML에서는 <img>, <input>, <br>과 같이 자식 요소가 없는 태그를 단일 태그로 작성할 수 있었습니다.

JSX에서는 이러한 태그를 사용할 때 항상 스스로 닫는(Self-closing) 형태로 작성해야 합니다.

// ⭕ 올바른 예시
<img src="logo.png" alt="로고" />
<input type="text" />
<br />

// ❌ 잘못된 예시
// <img src="logo.png" alt="로고">
// <input type="text">
// <br>

자식 요소가 있는 태그는 HTML과 동일하게 시작 태그와 종료 태그를 사용합니다.

<div>
  <p>이것은 단락입니다.</p>
</div>

자바스크립트 표현식 삽입: {} 중괄호

JSX 내부에 자바스크립트 변수, 함수 호출 결과, 또는 다른 자바스크립트 표현식을 삽입하고 싶다면 중괄호 {}를 사용합니다.

function Greeting() {
  const name = '김코딩';
  const age = 30;
  const isStudent = true;

  function formatGreeting(userName) {
    return `안녕하세요, ${userName}님!`;
  }

  return (
    <div>
      <h1>{formatGreeting(name)}</h1> {/* 함수 호출 결과 삽입 */}
      <p>나이: {age}</p> {/* 변수 값 삽입 */}
      {isStudent ? (
        <p>저는 학생입니다.</p>
      ) : (
        <p>저는 직장인입니다.</p>
      )} {/* 조건부 렌더링 (삼항 연산자) */}
      <p>현재 날짜: {new Date().toLocaleDateString()}</p> {/* 자바스크립트 객체 및 메서드 호출 */}
    </div>
  );
}
  • {변수명}: 변수의 값을 렌더링합니다.
  • {함수명()}: 함수의 반환값을 렌더링합니다.
  • {조건 ? 참일 때 : 거짓일 때}: 삼항 연산자를 사용하여 조건부 렌더링을 할 수 있습니다.
  • JSX 내에서 if 문이나 for 문과 같은 제어문은 직접 사용할 수 없습니다. 대신 삼항 연산자나 논리 연산자(&&, ||), 배열의 map() 메서드 등을 사용하여 조건을 처리하거나 반복적인 요소를 렌더링합니다. 이 부분은 뒤에서 더 자세히 다루겠습니다.

중괄호는 값을 계산할 자리이며, 어떤 값이든 화면의 자식으로 표시할 수 있는 자리는 아닙니다. 문자열·숫자는 표시할 수 있지만 일반 객체를 그대로 자식으로 놓으면 오류가 납니다. 객체의 속성이나 필요한 표시 문자열을 골라야 합니다. 객체를 style이나 컴포넌트의 입력으로 전달하는 것은 이 자식 표시 문제와 다릅니다. 중괄호와 객체의 공식 설명을 함께 확인할 수 있습니다.


JSX 내부의 주석

JSX 내부에서 주석을 작성할 때는 자바스크립트 주석 문법과 약간 다릅니다.

중괄호 {} 안에 자바스크립트 주석을 작성해야 합니다.

function CommentExample() {
  return (
    <div>
      {/* 이것은 JSX 내부의 주석입니다 */}
      <h1>JSX 주석 예시</h1>
      {
        // 여러 줄 주석도 가능합니다.
        // 이 주석은 화면에 렌더링되지 않습니다.
      }
      <p>주석은 코드를 설명하는 데 유용합니다.</p>
    </div>
  );
}

JSX의 활용 예시

간단한 예제를 통해 JSX의 활용법을 다시 한번 살펴보겠습니다.

function ProductCard(props) {
  const { name, price, imageUrl, isInStock } = props; // props를 비구조화 할당

  return (
    <div className="product-card">
      <img src={imageUrl} alt={name} className="product-image" />
      <h2>{name}</h2>
      <p>가격: {price}원</p>
      {isInStock ? ( // 조건부 렌더링
        <p style={{ color: 'green', fontWeight: 'bold' }}>재고 있음</p>
      ) : (
        <p style={{ color: 'red' }}>재고 없음</p>
      )}
      <button onClick={() => alert(`${name}을(를) 장바구니에 담았습니다!`)}>
        장바구니 담기
      </button>
    </div>
  );
}

export default ProductCard;

위 ProductCard 컴포넌트는 name, price, imageUrl, isInStock과 같은 데이터를 props로 받아 JSX를 통해 동적으로 UI를 구성하고 있습니다.

이 버튼은 알림만 표시하며, 실제 장바구니 데이터를 추가하거나 서버에 저장하지는 않습니다.

  • className="product-card": class 대신 className을 사용했습니다.
  • src={imageUrl}: 이미지 src 속성에 자바스크립트 변수 imageUrl을 삽입했습니다.
  • onClick={() => ...}: onClick 이벤트 핸들러에 자바스크립트 함수를 직접 전달했습니다.
  • style={{ color: 'green', fontWeight: 'bold' }}: style 속성에 자바스크립트 객체 형태로 CSS 속성을 직접 부여했습니다. (CSS 속성 이름도 카멜 케이스를 사용합니다. 예: font-weight -> fontWeight)

ProductCard 예제의 JSX를 읽을 때는 속성, 중괄호 표현식, 조건부 렌더링, 이벤트 핸들러가 어떤 역할을 나누어 맡는지 함께 확인하면 좋습니다.

JSX는 HTML처럼 보이지만 JavaScript 표현식입니다.

태그 구조, 중괄호 안 값, React 전용 속성 이름을 나누어 읽으면 오류 위치를 빠르게 좁힐 수 있습니다.

계산 결과를 먼저 예측하고 실행하기

앞 장 프로젝트의 src/App.jsx를 아래 코드로 바꾸고 저장합니다. 계산은 중괄호 안에서 일어나며, 따옴표로 감싼 속성이나 태그의 일반 텍스트는 그대로 전달됩니다.

src/App.jsx · JSX 계산 확인
export default function App() {
  const item = { name: '키보드', price: 30000 };
  return (
    <>
      <p>{item.name}: {item.price + 1000}원</p>
      <p>item.price + 1000</p>
      <p data-testid="total" aria-label="배송비 포함 가격">
        {item.price + 1000}
      </p>
    </>
  );
}

첫 문장은 키보드: 31000원, 둘째 문장은 item.price + 1000, 셋째 문장은 31000입니다. item 객체를 직접 표시한 것이 아니라, 문자열 속성과 숫자 계산 결과를 선택했습니다. 기존 ProductCard도 같은 방식으로 이름·가격·재고 분기 값을 읽습니다.

혼자 확인하기

위 코드의 상품을 이름 마우스, 가격 15000으로 바꿉니다. 세 문장을 먼저 적고 실행 결과와 대조하세요. 이어 첫 문장의 item.name을 item으로만 바꾸면 가격 계산 오류인지 자식 표시 오류인지 판단합니다. 오류를 확인한 뒤 원래 속성 접근으로 복원합니다.

계산과 오류 해설

세 문장은 마우스: 16000원, item.price + 1000, 16000입니다. 배송비 1000을 숫자로 더했고, 둘째 문장에는 중괄호가 없어 계산하지 않았습니다. item 자체는 일반 객체이므로 첫 문장의 자식으로 표시할 수 없어 렌더링 오류가 납니다. 더하기 계산의 문제가 아닙니다.

다음 절에서는 이 JSX를 재사용 가능한 UI 단위인 컴포넌트로 나누는 방법을 다룹니다.