React의 핵심인 컴포넌트를 만드는 법과, 이를 위해 사용하는 JSX 문법을 완벽히 익힌다.
컴포넌트(Component)는 React에서 UI를 만드는 기본 단위다. 레고 블록처럼 작은 부품들을 조립해서 전체 화면을 만든다고 생각하면 된다.
JSX(JavaScript XML)는 자바스크립트 안에서 HTML처럼 생긴 코드를 작성할 수 있게 해주는 문법이다.
React.createElement() 함수 호출로 변환됨// 컴포넌트: Greeting이라는 함수
function Greeting() {
// JSX: 이 컴포넌트가 그릴 UI를 JSX 문법으로 작성
return <h1>안녕하세요!</h1>;
}
// 위 JSX는 실제로 이렇게 변환됨
function Greeting() {
return React.createElement('h1', null, '안녕하세요!');
}
React 컴포넌트는 함수형 컴포넌트를 기본으로 사용한다. (과거에는 클래스형도 있었지만 지금은 거의 사용 안 함)
// 가장 간단한 컴포넌트
function Hello() {
return <div>안녕!</div>;
}
// 화살표 함수로도 작성 가능
const Hello = () => {
return <div>안녕!</div>;
};
// return이 한 줄이면 괄호 생략 가능
const Hello = () => <div>안녕!</div>;
Header, MyButton, UserProfileheader, myButton, userProfile<div>는 HTML div 태그, <Div>는 Div 컴포넌트
return해야 한다. 아무것도 반환하지 않으면 오류 발생.// 올바른 예
function Button() {
return <button>클릭</button>;
}
// 오류 발생 - 아무것도 반환 안 함
function Button() {
console.log("버튼");
}
export default로 내보내야 한다.// Button.jsx
function Button() {
return <button>클릭</button>;
}
export default Button; // 다른 파일에서 import 가능
// 또는 한 줄로
export default function Button() {
return <button>클릭</button>;
}
만든 컴포넌트는 HTML 태그처럼 사용한다. 이를 "컴포넌트 렌더링"이라고 한다.
// Button 컴포넌트 정의
function Button() {
return <button>클릭하세요</button>;
}
// App 컴포넌트에서 Button 사용
function App() {
return (
<div>
<h1>내 앱</h1>
<Button /> {/* Button 컴포넌트를 HTML 태그처럼 사용 */}
<Button /> {/* 여러 번 재사용 가능 */}
</div>
);
}
컴포넌트는 자바스크립트 함수이므로, 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>
);
}
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 />;
}
JSX는 HTML처럼 보이지만 실제로는 자바스크립트다. 그래서 HTML과는 다른 규칙들이 있다.
// 우리가 작성하는 JSX
const element = <h1 className="title">안녕!</h1>;
// Vite(Babel)가 변환한 실제 자바스크립트
const element = React.createElement(
'h1',
{ className: 'title' },
'안녕!'
);
JSX는 결국 React.createElement() 함수를 쉽게 쓰기 위한 문법 설탕(Syntactic Sugar)이다.
컴포넌트는 반드시 하나의 덩어리만 반환해야 한다. 형제 태그를 나란히 둘 수 없다.
// 오류 발생! - 두 개의 형제 태그
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의 완전한 형태 (key가 필요할 때만 사용)
import { Fragment } from 'react';
function App() {
return (
<Fragment>
<h1>제목</h1>
<p>내용</p>
</Fragment>
);
}
<div>가 많아지면 HTML 구조가 복잡해지고 CSS 스타일링이 어려워진다.<></>)는 실제 DOM에 렌더링되지 않으므로 깔끔한 HTML 구조를 유지할 수 있다.
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 />
<meta charset="UTF-8" />
중괄호 {} 안에 자바스크립트 표현식(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>
{/* 배열 (자동으로 join됨) */}
<p>{['사과', '바나나', '오렌지']}</p>
</div>
);
}
if문, for문, switch문 등의 문장(statement)// 오류! - if는 표현식이 아님
return <div>{if (true) { "참" }}</div>;
// 대신 삼항 연산자 사용
return <div>{true ? "참" : "거짓"}</div>;
JSX는 자바스크립트이므로, HTML 속성명이 약간 다르다.
| HTML | JSX | 이유 |
|---|---|---|
class |
className |
class는 JS 예약어 |
for |
htmlFor |
for는 JS 예약어 |
onclick |
onClick |
카멜케이스 사용 |
tabindex |
tabIndex |
카멜케이스 사용 |
// HTML 방식 (JSX에서는 오류!)
<div class="container">
<label for="name">이름</label>
<button onclick="handleClick()">클릭</button>
</div>
// JSX 방식
<div className="container">
<label htmlFor="name">이름</label>
<button onClick={handleClick}>클릭</button>
</div>
HTML에서는 문자열로 스타일을 지정했지만, JSX에서는 객체로 지정한다.
// HTML 방식
<div style="color: red; font-size: 20px;">텍스트</div>
// 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>;
function App() {
return (
<div>
{/* JSX 안에서 주석: 중괄호 안에 /* */ 사용 */}
<h1>제목</h1>
{/*
여러 줄 주석도 가능
이렇게 작성합니다
*/}
{// 한 줄 주석도 가능 (덜 일반적)
}
</div>
);
// 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 (
<>
{/* Fragment 사용 */}
<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;
하나의 파일(App.jsx)에 모든 코드를 넣는 것은 좋지 않다. 역할별로 파일을 나누면 코드 관리가 쉬워진다.
src 폴더에 다음 파일들을 만든다: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 style={{ border: '1px solid #ddd', padding: '20px' }}>
<h3>카드 2</h3>
<p>두 번째 카드 내용</p>
</div>
<div style={{ border: '1px solid #ddd', padding: '20px' }}>
<h3>카드 3</h3>
<p>세 번째 카드 내용</p>
</div>
</div>
</main>
);
}
export default MainContent;
src/Footer.jsx
function Footer() {
const year = new Date().getFullYear();
return (
<footer style={{
backgroundColor: '#f0f0f0',
padding: '20px',
textAlign: 'center',
borderTop: '1px solid #ddd'
}}>
<p>© {year} 내 웹사이트. All rights reserved.</p>
<div>
<a href="#privacy" style={{ margin: '0 10px' }}>개인정보처리방침</a>
<a href="#terms" style={{ margin: '0 10px' }}>이용약관</a>
</div>
</footer>
);
}
export default Footer;
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;
// 기본 내보내기 (Default Export) - 파일당 1개만 가능
export default function Button() {
return <button>클릭</button>;
}
// 불러올 때 - 이름을 마음대로 정할 수 있음
import Button from './Button';
import MyButton from './Button'; // 이름 변경 가능
import Btn from './Button'; // 이름 변경 가능
// 이름있는 내보내기 (Named Export) - 여러 개 가능
export function Button() {
return <button>클릭</button>;
}
export function Input() {
return <input />;
}
// 불러올 때 - 정확한 이름을 사용해야 함
import { Button, Input } from './Components';
import { Button as MyBtn } from './Components'; // as로 이름 변경 가능
// 혼합 사용
export default function Button() { /* ... */ }
export function SmallButton() { /* ... */ }
// 불러올 때
import Button, { SmallButton } from './Button';
상황에 따라 다른 UI를 보여줘야 할 때가 많다. JSX에서는 자바스크립트의 조건 연산자를 활용한다.
가장 많이 사용하는 방법. 참/거짓 두 가지 경우를 모두 처리할 때 사용한다.
function LoginButton() {
const isLoggedIn = false;
return (
<div>
{isLoggedIn ? (
<button>로그아웃</button>
) : (
<button>로그인</button>
)}
</div>
);
}
// 복잡한 UI도 가능
function UserGreeting() {
const user = { name: "철수", level: "프리미엄" };
const isLoggedIn = true;
return (
<div>
{isLoggedIn ? (
<div>
<h2>환영합니다, {user.name}님!</h2>
<span>회원 등급: {user.level}</span>
</div>
) : (
<div>
<h2>로그인이 필요합니다</h2>
<button>로그인하기</button>
</div>
)}
</div>
);
}
조건이 참일 때만 보여주고, 거짓이면 아무것도 안 보여줄 때 사용한다.
function Notification() {
const hasNewMessages = true;
const messageCount = 5;
return (
<div>
<h1>알림</h1>
{/* messageCount가 0보다 크면 표시 */}
{messageCount > 0 && (
<div>새 메시지 {messageCount}개</div>
)}
{/* hasNewMessages가 true면 표시 */}
{hasNewMessages && <span>🔔 새 알림이 있습니다</span>}
</div>
);
}
// 주의: 0은 화면에 렌더링됨!
function Example() {
const count = 0;
return (
<div>
{/* 🚨 잘못된 예: 0이 화면에 그대로 표시됨 */}
{count && <span>개수: {count}</span>}
{/* ✅ 올바른 예: 명확한 불리언 값 사용 */}
{count > 0 && <span>개수: {count}</span>}
</div>
);
}
조건이 복잡하면 JSX 밖에서 처리하고 변수에 저장하는 것이 더 깔끔하다.
function Dashboard() {
const user = {
name: "김철수",
age: 17,
isPremium: false
};
// JSX 밖에서 조건 처리
let content;
if (user.age < 18) {
content = <p>미성년자는 이용할 수 없습니다.</p>;
} else if (user.isPremium) {
content = (
<div>
<h2>프리미엄 회원 전용 콘텐츠</h2>
<p>모든 기능을 이용할 수 있습니다.</p>
</div>
);
} else {
content = (
<div>
<h2>일반 회원 콘텐츠</h2>
<button>프리미엄 업그레이드</button>
</div>
);
}
return (
<div>
<h1>대시보드</h1>
{content}
</div>
);
}
JSX 안에서 복잡한 조건을 처리하고 싶을 때 사용한다. (덜 일반적)
function ComplexCondition() {
const score = 85;
return (
<div>
<h1>성적표</h1>
{(() => {
if (score >= 90) return <span>🏆 A학점</span>;
if (score >= 80) return <span>👍 B학점</span>;
if (score >= 70) return <span>😊 C학점</span>;
return <span>😢 재수강 필요</span>;
})()}
</div>
);
}
function UserBadge() {
const user = {
name: "이영희",
level: "gold", // bronze, silver, gold, platinum
points: 1500
};
// 레벨별 스타일
const badgeStyles = {
bronze: { backgroundColor: '#cd7f32', color: 'white' },
silver: { backgroundColor: '#c0c0c0', color: 'black' },
gold: { backgroundColor: '#ffd700', color: 'black' },
platinum: { backgroundColor: '#e5e4e2', color: 'black' }
};
// 레벨별 아이콘
const getIcon = (level) => {
if (level === 'bronze') return '🥉';
if (level === 'silver') return '🥈';
if (level === 'gold') return '🥇';
if (level === 'platinum') return '💎';
return '⭐';
};
return (
<div style={{ padding: '20px' }}>
<div style={{
...badgeStyles[user.level],
padding: '10px 20px',
borderRadius: '20px',
display: 'inline-block',
fontWeight: 'bold'
}}>
{getIcon(user.level)} {user.level.toUpperCase()} 회원
</div>
<h2>{user.name}님</h2>
<p>포인트: {user.points}P</p>
{/* 다음 레벨까지 안내 */}
{user.level === 'bronze' && user.points >= 500 && (
<p style={{ color: 'green' }}>
✨ Silver 등급까지 {1000 - user.points}P 남았습니다!
</p>
)}
{user.level === 'platinum' ? (
<p>🎉 최고 등급입니다!</p>
) : (
<button>등급 업그레이드 방법 보기</button>
)}
</div>
);
}
export default UserBadge;
| 방법 | 사용 시기 | 예시 |
|---|---|---|
| 삼항 연산자 | 참/거짓 둘 다 처리 | {isOn ? "켜짐" : "꺼짐"} |
| AND 연산자 | 참일 때만 표시 | {hasError && "오류!"} |
| 변수 저장 | 복잡한 조건 | if/else로 처리 후 변수에 저장 |
| 즉시 실행 함수 | JSX 안에서 복잡한 로직 | {(() => { /* 로직 */ })()} |