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로 내보내야 한다.
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의 본질
// 우리가 작성하는 JSXconst 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>;