icon
2장 : React 기초

JSX 문법 이해하기


안녕하세요! 첫 번째 리액트 앱을 성공적으로 만들어보신 것을 다시 한번 축하드립니다. 1장 마지막에서 잠시 언급했듯이, 리액트 컴포넌트를 작성할 때 우리는 HTML과 매우 유사하게 생긴 특별한 문법을 사용했습니다. 바로 JSX(JavaScript XML) 입니다.

이번 장에서는 리액트 개발의 핵심이자 필수적인 JSX 문법에 대해 깊이 있게 알아보는 시간을 갖겠습니다. JSX는 처음 볼 때는 낯설게 느껴질 수 있지만, 익숙해지면 리액트 컴포넌트를 매우 직관적이고 효율적으로 작성할 수 있도록 돕는 강력한 도구입니다.


JSX란 무엇인가?

JSX는 JavaScript XML의 약자로, 이름에서 알 수 있듯이 자바스크립트 코드 내부에 XML/HTML과 유사한 구문을 작성할 수 있게 해주는 문법 확장입니다. 리액트에서 UI를 정의할 때 사용되며, 내부적으로는 자바스크립트 객체로 변환됩니다.

JSX의 특징

  • HTML처럼 보이지만 자바스크립트입니다: JSX는 겉보기에는 HTML 태그처럼 보이지만, 실제로는 자바스크립트 코드의 일부입니다. 덕분에 자바스크립트의 모든 기능을 JSX 내부에서 활용할 수 있습니다.
  • 컴파일 과정: 웹 브라우저는 JSX를 직접 이해하지 못합니다. 따라서 JSX 코드는 바벨(Babel)과 같은 트랜스파일러(Transpiler)에 의해 일반 자바스크립트 코드로 변환된 후 브라우저에서 실행됩니다. 우리가 create-react-app으로 프로젝트를 생성할 때 이미 이러한 변환 과정이 자동으로 설정되어 있습니다.
  • UI를 선언적으로 표현: JSX는 "무엇을 보여줄 것인가"를 직관적으로 선언할 수 있게 하여 UI 구조를 한눈에 파악하기 용이하게 만듭니다.

JSX의 기본 규칙

JSX는 HTML과 유사하지만 몇 가지 중요한 규칙을 따릅니다. 이 규칙들을 정확히 이해하는 것이 중요합니다.

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

리액트 컴포넌트는 JSX를 반환할 때, 반드시 하나의 최상위(Root) 요소로 감싸져야 합니다. 두 개 이상의 요소가 나란히 존재할 수 없습니다.

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

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

만약 불필요한 <div> 태그를 추가하고 싶지 않다면, 프래그먼트(Fragment) 를 사용할 수 있습니다. 프래그먼트는 <></> 또는 <React.Fragment></React.Fragment>와 같이 작성하며, 실제 DOM에는 렌더링되지 않는 '유령' 같은 요소입니다.

// ⭕ 올바른 예시: 프래그먼트 사용
import React from 'react'; // React.Fragment를 사용하려면 React를 임포트해야 합니다.

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

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

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

HTML 속성 이름의 CamelCase 규칙

JSX에서는 HTML 속성(Attribute) 이름을 작성할 때 자바스크립트의 CamelCase(카멜 케이스) 규칙을 따릅니다. 이는 자바스크립트에서 DOM 속성을 다룰 때도 유사한 규칙을 따르기 때문입니다.

  • class $\rightarrow$ className
  • for $\rightarrow$ htmlFor
  • tabindex $\rightarrow$ tabIndex
  • onclick $\rightarrow$ 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() 메서드 등을 사용하여 조건을 처리하거나 반복적인 요소를 렌더링합니다. 이 부분은 뒤에서 더 자세히 다루겠습니다.

JSX 내부의 주석

JSX 내부에서 주석을 작성할 때는 자바스크립트 주석 문법과 약간 다릅니다. 중괄호 {} 안에 자바스크립트 주석을 작성해야 합니다.

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

JSX의 활용 예시

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

import React from 'react';

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 $\rightarrow$ fontWeight)

JSX는 리액트 컴포넌트를 작성하는 데 있어 필수적인 문법입니다. 처음에는 규칙들이 낯설게 느껴질 수 있지만, 실제로 코드를 작성하면서 익숙해지는 것이 가장 좋습니다. 다음 장부터는 이 JSX를 활용하여 실제로 '컴포넌트'를 만들고, 데이터를 전달하고, 이벤트를 처리하는 방법에 대해 본격적으로 다루게 될 것입니다.


'JSX 문법 이해하기' 장은 여기까지입니다. JSX의 핵심 규칙들과 활용법을 명확하게 전달하고자 노력했습니다. 특히 카멜 케이스 규칙과 단일 루트 요소 반환, 그리고 중괄호를 이용한 자바스크립트 표현식 삽입에 대한 설명을 강조했습니다.

더 추가하거나 수정하고 싶은 부분이 있으신가요? 말씀해주시면 반영하겠습니다.