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: TRANSFORM & EXECUTE
JSX는 JS 코드로 변환되고 실행될 때 현재 값을 읽습니다
태그를 작성하는 시점과 name·price 같은 값이 평가되는 시점을 구분합니다. React element는 화면의 구조를 기술하는 값이며, 이후 React가 렌더 결과를 DOM에 반영합니다.
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: TRANSFORM & EXECUTE
JSX는 JS 코드로 변환되고 실행될 때 현재 값을 읽습니다
태그를 작성하는 시점과 name·price 같은 값이 평가되는 시점을 구분합니다. React element는 화면의 구조를 기술하는 값이며, 이후 React가 렌더 결과를 DOM에 반영합니다.
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->classNamefor->htmlFortabindex->tabIndexonclick->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를 아래 코드로 바꾸고 저장합니다. 계산은 중괄호 안에서 일어나며, 따옴표로 감싼 속성이나 태그의 일반 텍스트는 그대로 전달됩니다.
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 단위인 컴포넌트로 나누는 방법을 다룹니다.