Dev Thinking
32완료

HTML 안의 자바스크립트 - JSX 문법 공략

2025-08-04
7분 읽기

리액트 공식문서 기반의 리액트 입문기

들어가며

안녕하세요. 이번엔 리액트 개발의 핵심이자 가장 독특한 특징 중 하나인 JSX에 대해 탐구해 볼 시간입니다. JSX는 자바스크립트 확장 문법으로, 리액트 컴포넌트의 UI를 기술하는 데 사용됩니다. 처음 접하면 HTML 같기도 하고, 자바스크립트 같기도 해서 혼란스러울 수 있지만, 이 독특한 문법이야말로 리액트의 선언적인 UI 작성 방식을 가능하게 하는 핵심 요소입니다.

이번 편에서는 JSX가 무엇인지, 어떻게 사용하는지, 그리고 JSX가 제공하는 다양한 이점과 사용 시 주의할 점들을 자세히 알아보겠습니다.

리액트 UI 만들기

리액트에서는 자바스크립트 파일 안에서 HTML과 유사한 마크업을 작성하여 UI를 구성합니다. 이것이 바로 JSX입니다. 간단한 환영 메시지를 담은 UI를 리액트 JSX로 표현하면 다음과 같습니다.

// src/App.jsx (별도 파일로 가정)
export default function App() {
  return <h1>Hello, React with JSX!</h1>;
}
 
// src/main.jsx (애플리케이션 진입점 파일)
import App from './App';
 
const rootElement = document.getElementById('root');
if (rootElement) {
  ReactDOM.createRoot(rootElement).render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
}

JSX의 특징

리액트에서 JSX를 사용하여 UI를 구성하는 방식은 다음과 같은 특징을 가집니다.

  • 1. 자바스크립트 안의 마크업(Colocation)과 응집도 향상: JSX는 HTML과 자바스크립트(또는 UI 로직)를 분리하는 전통적인 웹 개발 방식과 달리, 자바스크립트 코드 내에서 마크업 구조를 함께 작성합니다. 이는 UI와 해당 UI의 동작 로직이 하나의 컴포넌트 안에서 함께 위치하게 되어 **응집도(Colocation)**를 높입니다. 개발자는 컴포넌트 하나만 보더라도 UI의 모습과 동작 방식을 동시에 이해할 수 있으므로, 코드의 가독성과 유지보수성이 크게 향상됩니다. 즉, 관심사를 파일 단위로 분리하는 것이 아니라 컴포넌트 단위로 묶는 리액트의 철학을 반영합니다.
  • 2. 선언형 UI 작성과 개발자의 사고 전환: JSX를 통해 개발자는 "무엇을" 그릴지 **선언적(Declarative)**으로 기술합니다. 즉, UI의 최종적인 상태와 모습을 정의하면, 리액트가 "어떻게" 그 상태를 실제 DOM에 반영할지 알아서 처리합니다. 이는 명령형으로 DOM을 직접 조작해야 했던 자바스크립트 방식과 대비되며, 개발자가 UI의 복잡한 구현 세부 사항보다는 애플리케이션의 핵심 로직과 UI의 의도에 더 집중할 수 있게 하여 개발 생산성과 코드의 예측 가능성을 높입니다.
  • 3. 향상된 가독성 및 개발 생산성: HTML과 매우 유사한 문법 덕분에 UI 구조를 한눈에 파악하기 쉽고, 자바스크립트의 강력한 기능을 중괄호({})를 통해 마크업 내에서 바로 활용할 수 있어 개발 생산성이 향상됩니다. 변수 삽입, 조건부 렌더링, 리스트 렌더링 등 동적인 UI 요소를 직관적으로 표현할 수 있으므로, 복잡한 UI도 간결하게 작성할 수 있게 됩니다.
  • 4. 컴포넌트 기반 개발 촉진과 재사용성: JSX는 UI의 각 부분을 독립적인 컴포넌트로 만들고, 이 컴포넌트들을 조합하여 전체 애플리케이션을 구축하는 방식을 자연스럽게 유도합니다. 각 컴포넌트가 JSX를 통해 자신의 UI를 명확하게 표현하므로, 컴포넌트의 역할과 경계가 분명해지고, 이는 곧 재사용성모듈화를 극대화합니다. Props를 통해 데이터와 로직을 전달하며 컴포넌트를 조립하는 방식은 대규모 애플리케이션의 개발과 관리를 용이하게 합니다.
  • 5. 보안 강화 (XSS 방지)와 안정성: 리액트는 JSX 내부에 삽입되는 값을 기본적으로 이스케이프(Escaping) 처리합니다. 이는 사용자로부터 입력받은 데이터를 UI에 직접 렌더링할 때 발생할 수 있는 악의적인 스크립트 주입 공격, 즉 XSS(Cross-Site Scripting) 공격을 효과적으로 방지합니다. 개발자가 별도의 보안 처리에 신경 쓰지 않아도 되므로, 애플리케이션의 보안을 강화하고 안정성을 높이는 데 크게 기여합니다.

JSX의 주요 규칙 및 제약사항

JSX는 HTML과 유사해 보이지만, 몇 가지 중요한 규칙과 제약사항이 있습니다. 이를 이해하는 것은 올바른 JSX 코드를 작성하는 데 필수적입니다.

1. 단일 루트 엘리먼트 (Single Root Element)

모든 JSX 엘리먼트는 반드시 하나의 부모 엘리먼트(Single Root Element) 안에 중첩되어야 합니다. 여러 엘리먼트를 나란히 반환할 수 없으며, 만약 여러 엘리먼트를 반환해야 한다면 <div>, <>, React.Fragment 등으로 감싸주어야 합니다.

왜 이렇게 해야 할까요?

리액트 컴포넌트는 UI를 표현하는 단 하나의 리액트 엘리먼트 객체를 반환해야 합니다. 이는 마치 함수가 단일 값을 반환하는 것과 유사합니다. 리액트가 렌더링 과정에서 컴포넌트가 반환한 이 단일 객체를 기반으로 UI 트리를 구성하고, 변경 사항을 효율적으로 감지(재조정, Reconciliation)하여 실제 DOM에 적용하기 위함입니다. 여러 개의 최상위 엘리먼트를 반환하면 리액트가 어떤 엘리먼트를 기준으로 업데이트를 처리해야 할지 명확히 판단할 수 없게 되므로, 컴포넌트의 예측 가능한 동작과 리액트의 안정적인 렌더링 메커니즘을 위해 이 규칙을 따릅니다.

// ❌ 잘못된 예시: 두 개의 엘리먼트가 최상위 레벨에 있습니다.
function WrongExample() {
  return (
    <h1>안녕하세요!</h1>
    <p>환영합니다.</p>
  );
}
 
// ✅ 올바른 예시: 하나의 div로 감싸져 있습니다.
function CorrectDivExample() {
  return (
    <div>
      <h1>안녕하세요!</h1>
      <p>환영합니다.</p>
    </div>
  );
}
 
// ✅ 올바른 예시: React.Fragment (단축 문법 <>)로 감싸져 있습니다.
function CorrectFragmentExample() {
  return (
    <>
      <h1>안녕하세요!</h1>
      <p>환영합니다.</p>
    </>
  );
}

2. 속성 명명 규칙 (CamelCase)

HTML에서 classfor와 같이 자바스크립트 예약어인 속성들은 JSX에서 className이나 htmlFor와 같이 카멜케이스(camelCase)로 작성해야 합니다. 이 외에도 tabindextabIndex, readonlyreadOnly 등으로 변경됩니다. 모든 DOM 속성은 카멜케이스로 작성하는 것이 규칙입니다.

// ❌ 잘못된 예시: class는 예약어입니다.
<label class="my-label" for="myInput">이름:</label>
<input id="myInput" type="text" readonly />
 
// ✅ 올바른 예시: className, htmlFor, readOnly 사용
<label className="my-label" htmlFor="myInput">이름:</label>
<input id="myInput" type="text" readOnly={true} />

3. 모든 태그는 닫혀야 합니다 (Self-Closing Tags)

HTML에서는 <img>, <input>, <br>과 같은 태그를 단독으로 사용할 수 있지만, JSX에서는 모든 태그를 반드시 닫아주어야 합니다. 내용이 없는 태그는 <img />와 같이 자체적으로 닫는 태그(Self-Closing Tag) 형식으로 작성해야 합니다.

// ❌ 잘못된 예시
<img src="avatar.png">
<input type="text">
 
// ✅ 올바른 예시
<img src="avatar.png" />
<input type="text" />

JSX의 핵심 기능 - 자바스크립트 표현식 활용 및 조건부 클래스명

JSX는 단순한 마크업을 넘어 자바스크립트의 강력한 기능을 활용할 수 있도록 해줍니다. 중괄호 {}를 사용하면 JSX 내부에 어떤 유효한 자바스크립트 표현식이든 삽입할 수 있습니다. 이는 동적인 데이터를 UI에 쉽게 반영하거나, 조건에 따라 다른 UI를 렌더링하는 등의 작업을 가능하게 합니다.

1. 자바스크립트 표현식 삽입

function Greeting(props) {
  const userName = '여러분';
  return (
    <div>
      <h1>Hello, {userName}!</h1>
      <p>오늘 날짜는 {new Date().toLocaleDateString()} 입니다.</p>
      <p>2 + 3 = {2 + 3} 입니다.</p>
    </div>
  );
}

2. 조건부 클래스명 활용

className에 자바스크립트 표현식을 사용하여 조건에 따라 다른 클래스를 적용할 수 있습니다. 예를 들어, isActive 상태에 따라 버튼의 스타일을 변경하는 경우를 생각해볼 수 있습니다.

function Button(props) {
  const isActive = props.isActive;
  return (
    <button className={isActive ? 'active-button' : 'inactive-button'}>{props.children}</button>
  );
}

JSX에서 리스트 렌더링하기

동적인 데이터 목록을 렌더링하는 것은 웹 애플리케이션에서 매우 흔한 패턴입니다. JSX에서는 자바스크립트의 map() 메서드를 활용하여 배열의 각 항목을 JSX 엘리먼트로 변환하여 렌더링할 수 있습니다. 이때 key prop을 사용하는 것이 중요합니다.

function ItemList(props) {
  const items = ['사과', '바나나', '오렌지'];
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li> // key prop 사용
      ))}
    </ul>
  );
}

주의: 간단한 예시를 위해 indexkey로 사용했지만, 실제 애플리케이션에서는 항목의 순서 변경/추가/삭제 시 예기치 못한 동작이 발생할 수 있습니다.

key prop의 중요성: key prop은 리액트가 리스트의 각 항목을 고유하게 식별하는 데 사용됩니다. key가 없거나 올바르지 않으면 리스트가 변경될 때(항목 추가, 삭제, 순서 변경 등) 성능 문제가 발생하거나 예상치 못한 UI 버그가 나타날 수 있습니다. 일반적으로 데이터의 고유 ID를 key로 사용하는 것이 가장 좋지만, 고유 ID가 없는 경우에는 index를 임시로 사용할 수 있습니다. (key prop에 대한 더 자세한 내용은 2-5편에서 다룹니다.)

JSX에서 스타일링하기

JSX에서 스타일을 적용하는 방법은 크게 두 가지가 있습니다.

1. 인라인 스타일 (Inline Styles)

HTML에서 style 속성에 문자열로 스타일을 작성하는 것과 달리, JSX에서는 자바스크립트 객체 형태로 스타일을 전달합니다. 이때 CSS 속성 이름은 카멜케이스로 작성해야 합니다 (예: background-color 대신 backgroundColor).

function StyledComponent() {
  const myStyle = {
    color: 'blue',
    fontSize: '16px',
    backgroundColor: 'lightgray',
  };
 
  return <p style={myStyle}>인라인 스타일이 적용된 텍스트입니다.</p>;
}

2. CSS 클래스 사용 (className)

가장 일반적이고 권장되는 방법은 외부 CSS 파일이나 CSS-in-JS를 통해 스타일을 정의하고, JSX 엘리먼트에 className을 부여하는 것입니다. 이 글에서는 CSS 코드를 포함하지 않고 시맨틱한 클래스명만 언급합니다. 예: <p className="highlight">텍스트</p> (스타일 정의는 별도 파일에서 관리)

JSX에 주석 추가하기

JSX 내부에 주석을 추가할 때는 자바스크립트 표현식처럼 {/* ... */} 형태를 사용합니다. 일반적인 자바스크립트 주석(// 또는 /* ... */)은 JSX 내부에서 직접 사용할 수 없습니다.

function CommentExample() {
  return (
    <div>
      {/* 이것은 JSX 내부의 주석입니다. */}
      <h1>주석 예시</h1>
      {/* <p>이 단락은 렌더링되지 않습니다.</p> */}
    </div>
  );
}

요약

지금까지 우리는 리액트 UI를 구성하는 핵심적인 방법인 JSX에 대해 깊이 있게 살펴보았습니다. 자바스크립트 코드 안에 마크업을 직접 작성할 수 있게 해주는 JSX는 선언적이고 재사용 가능한 컴포넌트 기반 개발을 가능하게 합니다. 이번 편에서 다룬 JSX의 주요 특징과 규칙, 그리고 자바스크립트 표현식, 리스트 렌더링, 스타일링, 주석 처리 등 실용적인 활용법들을 종합하면 다음과 같습니다.

  • JSX 본질: 자바스크립트 내 마크업 확장 문법으로, UI와 로직의 응집도 향상
  • 리액트 방식 특징: 선언형 UI, 가독성, 컴포넌트 기반 개발, XSS 방지 등
  • 핵심 규칙: 단일 루트 엘리먼트, 카멜케이스 속성, 모든 태그 닫기
  • 자바스크립트 표현식: 중괄호 {}로 동적 데이터 및 로직 삽입
  • 리스트 렌더링: map() 메서드 활용 및 key prop 중요성
  • 스타일링: 인라인 스타일(객체) 및 className (외부 CSS 연동)
  • 주석 처리: JSX 내부에서는 {/* ... */} 형태 사용

다음 편(2-3편)에서는 컴포넌트 간 데이터 전달의 핵심인 Props와 children을 살펴보겠습니다.

참고문서

– [JSX로 마크업 작성하기 (Writing Markup with JSX)]