JSX & 컴포넌트
함수 컴포넌트
React 컴포넌트는 JSX를 반환하는 JavaScript 함수입니다. 컴포넌트 이름은 대문자로 시작해야 합니다(소문자 = HTML 태그). Props는 속성으로 전달되고 매개변수에서 구조 분해됩니다. JSX는 React.createElement()의 구문적 설탕입니다. 항상 단일 root 요소를 반환하세요(또는 Fragment 사용). 컴포넌트는 순수해야 합니다 - 같은 props = 같은 출력.
// Basic function component
function Welcome({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Arrow function component
const Greeting = ({ name = 'Guest' }) => (
<p>Welcome, {name}!</p>
);
// Composing components
function App() {
return (
<div>
<Welcome name="Alice" />
<Welcome name="Bob" />
<Greeting />
</div>
);
}JSX 표현식
JSX는 중괄호 {} 안에 JavaScript 표현식을 포함할 수 있습니다. 변수, 함수 호출, 삼항 연산자, 값을 반환하는 모든 표현식을 넣을 수 있습니다. 문(if, for, switch)은 직접 허용되지 않습니다 - 삼항이나 IIFE를 사용하세요. 불린 값(true, false), null, undefined는 아무것도 렌더링하지 않습니다. 숫자와 문자열은 텍스트로 렌더링됩니다. 객체는 유효한 React 자식이 아닙니다.
function Expression({ user, items }) {
const fullName = user.first + ' ' + user.last;
const itemCount = items.length;
return (
<div>
{/* Expressions in curly braces */}
<h1>{fullName}</h1>
<p>{itemCount} items</p>
{/* Conditional */}
<p>{itemCount > 0 ? 'In stock' : 'Sold out'}</p>
{/* Method calls */}
<p>{fullName.toUpperCase()}</p>
{/* Numbers and booleans render as nothing */}
<p>{false}{null}{undefined}</p>
</div>
);
}Fragment & JSX 리스트
Fragment(<>...</>)는 추가 DOM 노드 없이 여러 요소를 그룹화합니다 - <div>로 감싸는 것보다 깔끔합니다. key를 전달해야 할 때 <Fragment key={...}>를 사용하세요. JSX 요소의 배열은 고유한 key prop이 필요합니다. 정적 리스트의 경우 배열 인덱스를 key로 사용할 수 있지만, 동적 리스트의 경우 렌더링 버그를 방지하기 위해 안정적인 ID를 사용하세요. Fragment는 불필요한 래퍼 요소를 줄여 성능을 향상합니다.
// Fragment: group without extra DOM node
function App() {
return (
<>
<header>Header</header>
<main>Content</main>
<footer>Footer</footer>
</>
);
}
// Array of elements (needs keys)
function List() {
const fruits = ['Apple', 'Banana', 'Cherry'];
return (
<ul>
{fruits.map((fruit, i) => (
<li key={i}>{fruit}</li>
))}
</ul>
);
}조건부 렌더링
React는 다양한 조건부 렌더링 패턴을 제공합니다. if/else 로직을 위한 early return. either/or를 위한 삼항(cond ? A : B). 표시/숨기기를 위한 논리 AND(cond && <Component/>). 복잡한 분기를 위한 IIFE. JSX에 복잡한 로직을 임베드하지 마세요 - 변수나 헬퍼 함수로 추출하세요. switch 문의 경우, 조회 객체를 사용하거나 별도의 함수로 추출하세요. 거짓 값(0, '')은 렌더링되므로, 숫자의 경우 && 대신 삼항을 사용하세요.
function Greeting({ isLoggedIn, user }) {
// 1. If/else (use early return)
if (!isLoggedIn) return <Login />;
// 2. Ternary operator
return (
<div>
{user ? <Dashboard user={user} /> : <Loading />}
{/* 3. Logical AND (render if truthy) */}
{user.isAdmin && <AdminPanel />}
{/* 4. IIFE for complex logic */}
{(() => {
if (user.role === 'admin') return <Admin />;
if (user.role === 'mod') return <Mod />;
return <User />;
})()}
</div>
);
}Children & Render Props
children prop은 여는 태그와 닫는 태그 사이의 요소를 포함합니다 - 조합 가능한 컴포넌트(카드, 모달, 레이아웃)에 필수적. render props는 데이터를 받고 JSX를 반환하는 함수를 prop으로 전달합니다 - 로직 공유를 위한 HOC와 hook의 대안. hook으로 render props가 덜 일반적이지만, 컴포넌트 주입 패턴에 여전히 유용합니다. children은 명시적으로 전달할 필요가 없는 특별한 prop입니다.
// children prop: content between tags
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
// Usage
<Card title="Profile">
<p>Name: Alice</p>
<p>Age: 30</p>
</Card>
// Render prop pattern
function DataProvider({ render }) {
const data = fetchData();
return <div>{render(data)}</div>;
}Props
Props 전달
Props는 부모에서 자식으로 전달되는 읽기 전용 데이터입니다. 모든 JavaScript 값이 될 수 있습니다: 문자열, 숫자, 불린, 배열, 객체, 또는 함수. 문자열 값은 따옴표를 사용하고(name='Alice'), 다른 모든 값은 중괄호를 사용합니다(age={30}). 함수를 prop으로 사용하면 자식에서 부모로의 통신(콜백)이 가능합니다. Props는 아래로 흐릅니다 - 자식은 props를 수정할 수 없습니다. 양방향 데이터 바인딩의 경우, 공통 부모로 state를 끌어올리세요.
// Parent passes props to child
function App() {
return (
<User
name="Alice"
age={30}
isActive={true}
tags={['admin', 'dev']}
onClick={() => console.log('clicked')}
/>
);
}
// Child receives props
function User({ name, age, isActive, tags, onClick }) {
return (
<div onClick={onClick}>
<h1>{name}</h1>
<p>Age: {age}</p>
<p>Status: {isActive ? 'Active' : 'Inactive'}</p>
</div>
);
}기본 & 선택적 Props
기본 prop 값은 구조 분해(param = defaultValue)로 설정됩니다. prop이 전달되 지 않으면 undefined입니다. 선택적 prop을 조건부로 렌더링하려면 short-circuit(bio && <p>)이나 삼항을 사용하세요. PropTypes(레거시)나 TypeScript 인터페이스로 개발 시 prop 타입을 검증할 수 있습니다. defaultProps(클래스 컴포넌트)는 함수 컴포넌트에서 더 이상 사용되지 않습니다 - 대신 구조 분해 기본값을 사용하세요.
// Default values via destructuring
function Button({ color = 'blue', size = 'md', children }) {
return (
<button className={'btn btn-' + color + ' btn-' + size}>
{children}
</button>
);
}
// Optional props (undefined if not passed)
function Profile({ name, bio }) {
return (
<div>
<h1>{name}</h1>
{bio && <p>{bio}</p>}
</div>
);
}
// Usage
<Button>Click</Button> {/* color='blue', size='md' */}
<Profile name="Alice" /> {/* bio is undefined */}Spread & Rest Props
spread 연산자(...props)는 모든 prop을 자식 요소로 전달합니다 - 래퍼 컴포넌트(HOC, styled component)에 유용. rest 연산자는 특정 prop을 구조 분해한 후 남은 prop을 수집합니다. 이 패턴은 래퍼 컴포넌트가 알 수 없는 prop을 DOM 요소로 전달하는 디자인 시스템에서 일반적입니다. 주의: spread는 명시적 속성을 재정의할 수 있습니다 - 순서가 중요합니다({...props} className='x' vs className='x' {...props}).
// Spread: pass all props to child
function Input(props) {
return <input {...props} className="input" />;
}
// Usage
<Input type="text" placeholder="Name" value="Alice" />
// Rest: collect remaining props
function Button({ label, ...rest }) {
return <button {...rest}>{label}</button>;
}
// Selective spreading
function Card({ title, children, ...divProps }) {
return (
<div {...divProps}>
<h2>{title}</h2>
{children}
</div>
);
}Prop Types & TypeScript
TypeScript 인터페이스는 prop에 대한 컴파일 타임 타입 검사를 제공합니다 - 새 React 프로젝트에 권장되는 접근 방식. 선택적 prop은 ?를 사용합니다(isActive?: boolean). PropTypes는 런타임 검증(개발 중에만)을 제공하며 TypeScript가 없는 JavaScript 프로젝트에 유용합니다. isRequired는 prop이 제공되었는지 보장합니다. TypeScript는 런타임 전에 타입 오류를 잡아내어 대규모 코드베이스에 우수합니다.
// TypeScript interface (recommended)
interface UserProps {
name: string;
age: number;
isActive?: boolean; // optional
onClick: (id: number) => void;
}
function User({ name, age, isActive = true, onClick }: UserProps) {
return <div onClick={() => onClick(1)}>{name}, {age}</div>;
}
// PropTypes (runtime checking, legacy)
import PropTypes from 'prop-types';
User.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
isActive: PropTypes.bool,
};Prop Drilling & Context
prop drilling은 prop을 사용하지 않는 여러 컴포넌트 계층을 통과할 때 발생합니다. 2-3 수준의 경우 허용 가능합니다. 더 깊은 트리의 경우 Context API, 상태 관리 라이브러리(Redux, Zustand), 또는 컴포넌트 조합을 사용하세요. 조합(컴포넌트를 prop이나 children으로 전달)이 종종 Context보다 더 우아하게 drilling을 해결합니다. 질문: 모든 중간 컴포넌트가 이 데이터가 필요한가? 그렇지 않다면, 컴포넌트 구조를 재고하세요.