3 JSX 문법과 컴포넌트
1. JSX 문법과 컴포넌트
React의 핵심인 컴포넌트를 만드는 법과, 이를 위해 사용하는 JSX 문법을 완벽히 익힌다.
1.1. 컴포넌트와 JSX의 관계
1.1.1. 컴포넌트란?
**컴포넌트(Component)**는 React에서 UI를 만드는 기본 단위다. 레고 블록처럼 작은 부품들을 조립해서 전체 화면을 만든다고 생각하면 된다.
- 자바스크립트 함수: React 컴포넌트는 그냥 자바스크립트 함수다
- UI 반환: 이 함수는 화면에 그릴 내용을 반환한다
- 재사용 가능: 한 번 만들면 여러 곳에서 사용할 수 있다
1.1.2. JSX란?
**JSX(JavaScript XML)**는 자바스크립트 안에서 HTML처럼 생긴 코드를 작성할 수 있게 해주는 문법이다.
- HTML + JavaScript: HTML 구조 안에 자바스크립트 코드를 넣을 수 있다
- 변환 필요: 브라우저는 JSX를 이해 못함 → Vite(Babel)가 일반 JS로 변환
- 문법 설탕: 실제로는
React.createElement()함수 호출로 변환됨
1.1.3. 둘의 관계
요약
핵심 개념:
컴포넌트는 **“무엇”**이고, JSX는 **“어떻게”**다.
• 컴포넌트: UI를 만드는 함수 (설계도) • JSX: 컴포넌트 안에서 UI를 표현하는 문법 (재료와 조립법)
컴포넌트는 JSX를 반환(return)함으로써 화면에 무엇을 그릴지 정의한다.
// 컴포넌트: Greeting이라는 함수
function Greeting() {
// JSX: 이 컴포넌트가 그릴 UI를 JSX 문법으로 작성
return <h1>안녕하세요!</h1>;
}
// 위 JSX는 실제로 이렇게 변환됨
function Greeting() {
return React.createElement('h1', null, '안녕하세요!');
}
1.2. 컴포넌트 완전 정복
1.2.1. 컴포넌트의 기본 구조
React 컴포넌트는 함수형 컴포넌트를 기본으로 사용한다. (과거에는 클래스형도 있었지만 지금은 거의 사용 안 함)
// 가장 간단한 컴포넌트
function Hello() {
return <div>안녕!</div>;
}
// 화살표 함수로도 작성 가능
const Hello = () => {
return <div>안녕!</div>;
};
// return이 한 줄이면 괄호 생략 가능
const Hello = () => <div>안녕!</div>;
1.2.2. 컴포넌트 작성 규칙 (매우 중요!)
요약
규칙 1: 대문자로 시작
컴포넌트 이름은 반드시 대문자로 시작해야 한다.
올바른 예: Header, MyButton, UserProfile
잘못된 예: header, myButton, userProfile
이유: React는 소문자로 시작하면 일반 HTML 태그로 인식한다.
<div>는 HTML div 태그, <Div>는 Div 컴포넌트
요약
규칙 2: JSX 반환
함수는 반드시 JSX를 return해야 한다. 아무것도 반환하지 않으면 오류 발생.
// 올바른 예
function Button() {
return <button>클릭</button>;
}
// 오류 발생 - 아무것도 반환 안 함
function Button() {
console.log("버튼");
}요약
규칙 3: 파일당 하나의 기본 내보내기
다른 파일에서 사용하려면 export default로 내보내야 한다.
// Button.jsx
function Button() {
return <button>클릭</button>;
}
export default Button; // 다른 파일에서 import 가능
// 또는 한 줄로
export default function Button() {
return <button>클릭</button>;
}1.2.3. 컴포넌트 사용하기
만든 컴포넌트는 HTML 태그처럼 사용한다. 이를 **“컴포넌트 렌더링”**이라고 한다.
// Button 컴포넌트 정의
function Button() {
return <button>클릭하세요</button>;
}
// App 컴포넌트에서 Button 사용
function App() {
return (
<div>
<h1>내 앱</h1>
<Button /> {/* Button 컴포넌트를 HTML 태그처럼 사용 */}
<Button /> {/* 여러 번 재사용 가능 */}
</div>
);
}
1.2.4. 컴포넌트 안에서 로직 작성하기
컴포넌트는 자바스크립트 함수이므로, return 전에 일반 JS 코드를 작성할 수 있다.
function Greeting() {
// return 전에 자바스크립트 로직 작성
const name = "철수";
const hour = new Date().getHours();
const greeting = hour < 12 ? "좋은 아침" : "안녕하세요";
// JSX에서 변수 사용
return (
<div>
<h1>{greeting}, {name}님!</h1>
<p>현재 시각은 {hour}시입니다.</p>
</div>
);
}
요약
[실습 1] 자기소개 컴포넌트 만들기
src/Profile.jsx 파일을 만들고 다음 내용을 작성:
function Profile() {
const name = "홍길동";
const age = 25;
const hobby = "독서";
return (
<div style={{ padding: '20px', border: '1px solid #ccc' }}>
<h2>{name}의 프로필</h2>
<p>나이: {age}세</p>
<p>취미: {hobby}</p>
</div>
);
}
export default Profile;그리고 App.jsx에서 불러와서 사용:
import Profile from './Profile';
function App() {
return <Profile />;
}1.3. JSX 문법 완전 정복
JSX는 HTML처럼 보이지만 실제로는 자바스크립트다. 그래서 HTML과는 다른 규칙들이 있다.
1.3.1. JSX의 본질
// 우리가 작성하는 JSX
const element = <h1 className="title">안녕!</h1>;
// Vite(Babel)가 변환한 실제 자바스크립트
const element = React.createElement(
'h1',
{ className: 'title' },
'안녕!'
);
JSX는 결국 React.createElement() 함수를 쉽게 쓰기 위한 **문법 설탕(Syntactic Sugar)**이다.
1.3.2. JSX 규칙 1: 하나의 최상위 태그
컴포넌트는 반드시 하나의 덩어리만 반환해야 한다. 형제 태그를 나란히 둘 수 없다.
// 오류 발생! - 두 개의 형제 태그
function App() {
return (
<h1>제목</h1>
<p>내용</p>
);
}
// 해결책 1: div로 감싸기
function App() {
return (
<div>
<h1>제목</h1>
<p>내용</p>
</div>
);
}
// 해결책 2: Fragment 사용 (권장)
function App() {
return (
<> {/* Fragment: 화면에 렌더링되지 않는 투명한 태그 */}
<h1>제목</h1>
<p>내용</p>
</>
);
}
요약
Fragment를 쓰는 이유:
불필요한 <div>가 많아지면 HTML 구조가 복잡해지고 CSS 스타일링이 어려워진다.
Fragment(<></>)는 실제 DOM에 렌더링되지 않으므로 깔끔한 HTML 구조를 유지할 수 있다.
1.3.3. JSX 규칙 2: 모든 태그는 닫아야 함
HTML에서는 일부 태그를 닫지 않아도 됐지만, JSX에서는 모든 태그를 반드시 닫아야 한다.
// HTML에서는 OK, JSX에서는 오류
<input type="text">
<br>
<img src="image.jpg">
// JSX에서는 이렇게 닫아야 함 (Self-closing tag)
<input type="text" />
<br />
<img src="image.jpg" />
<hr />
1.3.4. JSX 규칙 3: 중괄호로 자바스크립트 표현식 삽입
중괄호 {} 안에 자바스크립트 **표현식(expression)**을 넣을 수 있다.
function Welcome() {
const name = "철수";
const age = 20;
const isAdult = age >= 18;
return (
<div>
{/* 변수 */}
<h1>안녕, {name}!</h1>
{/* 계산식 */}
<p>10년 후 나이: {age + 10}</p>
{/* 삼항 연산자 */}
<p>{isAdult ? "성인" : "미성년자"}</p>
{/* 함수 호출 */}
<p>대문자 이름: {name.toUpperCase()}</p>
</div>
);
}
요약
중요: 표현식만 가능!
중괄호 안에는 값을 반환하는 표현식만 넣을 수 있다.
올바른 예: 변수, 계산식, 삼항 연산자, 함수 호출
잘못된 예: if문, for문, switch문 등의 문장(statement)
// 오류! - if는 표현식이 아님
return <div>{if (true) { "참" }}</div>;
// 대신 삼항 연산자 사용
return <div>{true ? "참" : "거짓"}</div>;1.3.5. JSX 규칙 4: 속성명 주의사항
JSX는 자바스크립트이므로, HTML 속성명이 약간 다르다.
| HTML | JSX | 이유 |
|---|---|---|
class | className | class는 JS 예약어 |
for | htmlFor | for는 JS 예약어 |
onclick | onClick | 카멜케이스 사용 |
tabindex | tabIndex | 카멜케이스 사용 |
// JSX 방식
<div className="container">
<label htmlFor="name">이름</label>
<button onClick={handleClick}>클릭</button>
</div>
1.3.6. JSX 규칙 5: 인라인 스타일은 객체로
HTML에서는 문자열로 스타일을 지정했지만, JSX에서는 객체로 지정한다.
// JSX 방식 (객체 사용, 속성명은 카멜케이스)
<div style={{ color: 'red', fontSize: '20px' }}>텍스트</div>
// ↑ 첫 번째 중괄호: JS 표현식
// ↑ 두 번째 중괄호: 객체 리터럴
// 변수로 분리하면 더 깔끔
const myStyle = {
color: 'red',
fontSize: '20px',
backgroundColor: '#f0f0f0' // background-color → backgroundColor
};
return <div style={myStyle}>텍스트</div>;
1.3.7. JSX 규칙 6: 주석 작성법
function App() {
return (
<div>
{/* JSX 안에서 주석: 중괄호 안에 /* */ 사용 */}
<h1>제목</h1>
{/*
여러 줄 주석도 가능
이렇게 작성합니다
*/}
</div>
);
}
요약
[실습 2] JSX 문법 연습
src/Card.jsx 파일을 만들고 JSX 문법을 연습해보자:
function Card() {
const title = "React 학습";
const progress = 75;
const completed = progress >= 100;
const cardStyle = {
border: '2px solid #007bff',
borderRadius: '10px',
padding: '20px',
margin: '10px',
backgroundColor: completed ? '#d4edda' : '#fff3cd'
};
return (
<>
<div style={cardStyle}>
<h2 className="card-title">{title}</h2>
<p>진행률: {progress}%</p>
{/* 진행 바 */}
<div style={{
width: '100%',
height: '20px',
backgroundColor: '#e0e0e0',
borderRadius: '10px'
}}>
<div style={{
width: `${progress}%`,
height: '100%',
backgroundColor: '#007bff',
borderRadius: '10px'
}} />
</div>
{/* 조건부 렌더링 */}
<p>{completed ? "✅ 완료!" : "🔄 진행 중"}</p>
</div>
</>
);
}
export default Card;1.4. 컴포넌트 분리 실습
하나의 파일(App.jsx)에 모든 코드를 넣는 것은 좋지 않다. 역할별로 파일을 나누면 코드 관리가 쉬워진다.
- 재사용성: 한 번 만든 컴포넌트를 여러 곳에서 사용
- 유지보수: 문제가 생기면 해당 컴포넌트 파일만 수정
- 협업: 팀원들이 각자 다른 컴포넌트를 작업 가능
- 테스트: 개별 컴포넌트를 독립적으로 테스트 가능
요약
언제 분리해야 할까?
- 재사용될 것 같은 UI: 버튼, 카드, 입력 폼 등
- 독립적인 기능: 로그인 폼, 검색 바, 장바구니 등
- 너무 길어진 컴포넌트: 100줄 이상이면 분리 고려
- 명확한 책임이 있는 UI: 헤더, 푸터, 사이드바 등
요약
[실습 3] 컴포넌트 분리하기
1단계: 컴포넌트 파일 생성
src/Header.jsx
function Header() {
return (
<header style={{
backgroundColor: '#282c34',
padding: '20px',
color: 'white'
}}>
<h1>내 웹사이트</h1>
<nav>
<a href="#home" style={{ color: 'white', margin: '0 10px' }}>홈</a>
<a href="#about" style={{ color: 'white', margin: '0 10px' }}>소개</a>
<a href="#contact" style={{ color: 'white', margin: '0 10px' }}>연락</a>
</nav>
</header>
);
}
export default Header;src/MainContent.jsx
function MainContent() {
return (
<main style={{
padding: '40px',
minHeight: '500px'
}}>
<h2>메인 콘텐츠</h2>
<p>여기에 주요 내용이 들어갑니다.</p>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '20px',
marginTop: '20px'
}}>
<div style={{ border: '1px solid #ddd', padding: '20px' }}>
<h3>카드 1</h3>
<p>첫 번째 카드 내용</p>
</div>
</div>
</main>
);
}
export default MainContent;2단계: App.jsx에서 조립하기
import Header from './Header';
import MainContent from './MainContent';
import Footer from './Footer';
function App() {
return (
<div style={{
display: 'flex',
flexDirection: 'column',
minHeight: '100vh'
}}>
<Header />
<MainContent />
<Footer />
</div>
);
}
export default App;1.5. 조건부 렌더링
상황에 따라 다른 UI를 보여줘야 할 때가 많다. JSX에서는 자바스크립트의 조건 연산자를 활용한다.
1.5.1. 방법 1: 삼항 연산자 (조건 ? 참 : 거짓)
참/거짓 두 가지 경우를 모두 처리할 때 사용한다.
{isLoggedIn ? (
<button>로그아웃</button>
) : (
<button>로그인</button>
)}
1.5.2. 방법 2: AND 연산자 (&&)
조건이 참일 때만 보여주고, 거짓이면 아무것도 안 보여줄 때 사용한다.
{messageCount > 0 && (
<div>새 메시지 {messageCount}개</div>
)}
1.5.3. 방법 3: 변수에 저장하기
조건이 복잡하면 JSX 밖에서 처리하고 변수에 저장하는 것이 더 깔끔하다.
let content;
if (user.age < 18) {
content = <p>미성년자는 이용할 수 없습니다.</p>;
} else {
content = <p>환영합니다!</p>;
}
return <div>{content}</div>;