지난 시간에는 Vite로 첫 리액트 프로젝트를 만들고 개발 서버를 띄웠습니다. 그 과정에서 우리는 자연스럽게 다음과 같은 코드를 봤습니다.
function App() {
return (
<div>
<h1>안녕하세요, 리액트!</h1>
</div>
);
}함수 안에서 HTML처럼 생긴 코드를 그냥 return하고 있습니다. 자바스크립트 안에 HTML이라니, 어딘가 어색하지 않으신가요? 이번 시간에는 이 신기한 문법인 JSX가 무엇이고, 어떻게 사용하는지 알아보도록 하겠습니다.
JSX는 무엇인가?
**JSX(JavaScript XML)**는 자바스크립트에 HTML과 비슷한 마크업 문법을 합쳐 놓은 것입니다. 자바스크립트의 공식 문법은 아니지만, 페이스북이 리액트를 발표할 때 함께 제안한 확장 문법이며, 오늘날 거의 모든 리액트 프로젝트가 JSX를 사용합니다.
사실 JSX 없이도 리액트 코드를 작성할 수 있습니다. 다음 두 코드는 정확히 동일한 결과를 만듭니다.
function App() {
return <h1>Hello</h1>;
}function App() {
return React.createElement('h1', null, 'Hello');
}위쪽이 훨씬 읽기 쉽다는 데 누구나 동의할 것입니다. 그래서 우리는 JSX를 사용합니다.
브라우저는 JSX를 모른다
브라우저의 자바스크립트 엔진은 JSX를 직접 이해하지 못합니다. 우리가 JSX로 작성한 코드는 빌드 과정에서 일반 자바스크립트로 변환된 후 브라우저로 전달됩니다. 지난 시간에 사용한 Vite 같은 빌드 도구가 이 변환을 자동으로 처리해주기 때문에, 우리는 신경쓰지 않고 JSX를 마음껏 사용할 수 있습니다.
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를 사용할 수 있습니다.
function App() {
return (
<>
<h1>안녕하세요</h1>
<p>오늘도 즐거운 하루 보내세요</p>
</>
);
}<>...</>는 React.Fragment의 짧은 표기로, 실제 DOM에는 어떤 요소도 만들지 않으면서 여러 자식을 묶어주는 역할을 합니다.
규칙 2. 모든 태그는 닫혀야 한다
HTML에서는 <img>, <br>, <input> 같은 일부 태그를 닫지 않고 사용해도 됐습니다. 하지만 JSX에서는 모든 태그를 닫아야 합니다. 자식이 없는 태그는 끝에 /를 붙여 자체 닫음(self-closing)으로 처리합니다.
<img src="/cat.png" alt="고양이" />
<br />
<input type="text" />규칙 3. class 대신 className
HTML에서 CSS 클래스를 지정할 때 사용하던 class 속성은 자바스크립트의 예약어와 겹치기 때문에 JSX에서는 **className**을 사용합니다.
<div className="card">카드입니다</div>같은 이유로 <label>의 for 속성도 htmlFor로 바뀝니다.
<label htmlFor="email">이메일</label>
<input id="email" type="email" />규칙 4. 속성 이름은 camelCase
HTML에서는 속성 이름이 모두 소문자였지만 (onclick, tabindex, readonly), JSX에서는 camelCase를 사용합니다.
<button onClick={handleClick} tabIndex={0}>클릭</button>
<input readOnly value="고정값" />JSX 안에 자바스크립트 표현식 넣기
JSX의 진짜 힘은 마크업 안에 자바스크립트 표현식을 자유롭게 끼워 넣을 수 있다는 데 있습니다. 중괄호 { } 안에 들어가는 모든 자바스크립트 표현식은 평가되어 그 결과가 화면에 출력됩니다.
function App() {
const name = '철수';
const age = 30;
return (
<div>
<h1>안녕하세요, {name}님!</h1>
<p>나이: {age}세</p>
<p>10년 후에는: {age + 10}세</p>
</div>
);
}함수 호출도, 삼항 연산자도, 배열의 메소드도 모두 표현식이라면 사용할 수 있습니다.
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입니다.
function App() {
return (
<h1 style={{ color: 'tomato', fontSize: '32px' }}>
스타일 적용된 제목
</h1>
);
}중괄호가 두 개인 것이 처음에는 어색해 보일 수 있는데, 바깥 { }는 "JSX 안의 자바스크립트 표현식"을 의미하고, 안쪽 { }는 "자바스크립트 객체 리터럴"이기 때문입니다.
JSX 안의 주석
JSX 안에서 주석을 달려면 자바스크립트 표현식이므로 중괄호로 감싸야 합니다.
function App() {
return (
<div>
{/* 이것은 JSX 주석입니다 */}
<h1>제목</h1>
</div>
);
}직접 해보기
지난 시간에 만든 프로젝트의 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가지 규칙 중 하나를 깜빡한 경우입니다. "여러 요소를 부모로 감쌌나?", "태그를 모두 닫았나?", "class를 className으로 썼나?", "속성을 camelCase로 썼나?" 순서로 점검해보면 대부분 해결됩니다.
마무리
이번 글에서는 리액트의 핵심 문법인 JSX를 소개하고, 기본 규칙 4가지(부모 요소로 감싸기, 태그 닫기, className/htmlFor, camelCase 속성), JS 표현식 임베딩, 인라인 스타일, 주석을 살펴봤습니다. 이제 JSX 코드를 보고 어색함이 많이 줄었기를 바랍니다.
다음 글인 "리액트 기초 강좌 #4 컴포넌트와 props"에서는 화면을 작은 단위로 나누는 핵심 개념인 컴포넌트와, 컴포넌트에 데이터를 전달하는 통로인 props를 다뤄보도록 하겠습니다.