⚛️ React란?
📖 정의
React는 Facebook(현 Meta)에서 개발한 사용자 인터페이스(UI)를 만들기 위한 JavaScript 라이브러리입니다. 컴포넌트 기반으로 재사용 가능한 UI를 작성하며, Virtual DOM을 사용하여 효율적으로 화면을 업데이트합니다. 선언적 프로그래밍 방식으로 복잡한 UI를 간단하 게 관리할 수 있습니다.
🎯 비유로 이해하기
레고 블록
React 컴포넌트를 레고 블록에 비유하면:
레고 블록 = React 컴포넌트
├─ 작은 블록들을 조합
├─ 같은 블록을 여러 번 재사용
├─ 큰 구조물을 쉽게 조립
└─ 필요하면 블록만 교체
예시:
<House> ← 집 전체
<Roof /> ← 지붕 블록
<Wall /> ← 벽 블록
<Door /> ← 문 블록
<Window /> ← 창문 블록
</House>
각 블록(컴포넌트)은 독립적이고 재사용 가능!
설계도면 vs 실제 건물
설계도면 (Virtual DOM)
- 메모리에만 존재
- 변경사항을 먼저 설계도에 반영
- 차이점만 계산
실제 건물 (Real DOM)
- 브라우저 화면
- 최소한의 변경만 적용
- 효율적인 업데이트
React는 설계도를 먼저 수정하고,
차이나는 부분만 실제 건물에 반영!
⚙️ 작동 원리
1. 컴포넌트 구조
// 함수형 컴포넌트 (현대 방식)
function Welcome(props) {
return <h1>안녕하세요, {props.name}님!</h1>;
}
// 사용
<Welcome name="김철수" />
// 컴포넌트 트리
<App>
├─ <Header>
│ ├─ <Logo />
│ └─ <Navigation />
├─ <Main>
│ ├─ <Sidebar />
│ └─ <Content>
│ ├─ <Article />
│ └─ <Comments>
│ └─ <Comment />
└─ <Footer />
2. Virtual DOM 작동 원리
// 1. 초기 렌더링
Virtual DOM: <div>Count: 0</div>
Real DOM: <div>Count: 0</div>
// 2. 상태 변경 (count = 1)
New Virtual DOM: <div>Count: 1</div>
Old Virtual DOM: <div>Count: 0</div>
// 3. Diffing (차이 계산)
변경된 부분: 텍스트 "0" → "1"
// 4. 실제 DOM에 최소 변경만 적용
Real DOM: <div>Count: 1</div>
오직 텍스트만 변경! (div는 그대로)
3. 데이터 흐름 (단방향)
부모 → 자식 (Props로 전달)
<Parent>
↓ props
<Child data={parentData} />
↓ props
<GrandChild data={childData} />
역방향 통신: 콜백 함수 사용
<Parent>
<Child onUpdate={handleUpdate} />
자식이 onUpdate() 호출하면
부모의 handleUpdate 실행
💡 실제 예시
기본 컴포넌트
import React from 'react';
// 함수형 컴포넌트
function Greeting({ name, age }) {
return (
<div>
<h1>안녕하세요, {name}님!</h1>
<p>나이: {age}세</p>
</div>
);
}
// 사용
function App() {
return (
<div>
<Greeting name="김철수" age={25} />
<Greeting name="이영희" age={30} />
</div>
);
}
export default App;
State (상태 관리)
import { useState } from 'react';
function Counter() {
// useState Hook
const [count, setCount] = useState(0); // 초기값 0
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>
증가
</button>
<button onClick={() => setCount(count - 1)}>
감소
</button>
<button onClick={() => setCount(0)}>
리셋
</button>
</div>
);
}
// State가 변경되면 컴포넌트 자동 재렌더링!
Props vs State
// Props (부모 → 자식, 읽기 전용)
function Child({ message }) {
// message는 변경 불가
return <p>{message}</p>;
}
function Parent() {
return <Child message="안녕!" />;
}
// State (컴포넌트 내부, 변경 가능)
function Component() {
const [message, setMessage] = useState("안녕!");
return (
<div>
<p>{message}</p>
<button onClick={() => setMessage("반가워!")}>
메시지 변경
</button>
</div>
);
}
useEffect (부수 효과)
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// 컴포넌트가 마운트되거나 userId가 변경될 때 실행
useEffect(() => {
console.log('useEffect 실행!');
// API 호출
fetch(`https://api.example.com/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
// 클린업 함수 (컴포넌트 언마운트 시)
return () => {
console.log('클린업!');
};
}, [userId]); // 의존성 배열: userId 변경 시에만 재실행
if (loading) return <div>로딩 중...</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
리스트 렌더링
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '운동하기', done: false },
{ id: 2, text: '공부하기', done: true },
{ id: 3, text: '청소하기', done: false }
]);
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};
return (
<ul>
{todos.map(todo => (
<li
key={todo.id} // key는 필수!
style={{
textDecoration: todo.done ? 'line-through' : 'none'
}}
onClick={() => toggleTodo(todo.id)}
>
{todo.text}
</li>
))}
</ul>
);
}
조건부 렌더링
function LoginStatus({ isLoggedIn, username }) {
// 방법 1: if문
if (isLoggedIn) {
return <p>환영합니다, {username}님!</p>;
} else {
return <button>로그인</button>;
}
// 방법 2: 삼항 연산자
return isLoggedIn ? (
<p>환영합니다, {username}님!</p>
) : (
<button>로그인</button>
);
// 방법 3: && 연산자 (조건이 true일 때만 렌더링)
return (
<div>
{isLoggedIn && <p>환영합니다, {username}님!</p>}
{!isLoggedIn && <button>로그인</button>}
</div>
);
}
폼 처리
function LoginForm() {
const [formData, setFormData] = useState({
username: '',
password: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value
});
};
const handleSubmit = (e) => {
e.preventDefault(); // 페이지 새로고침 방지
console.log('로그인 시도:', formData);
// API 호출
fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
placeholder="사용자명"
/>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
placeholder="비밀번호"
/>
<button type="submit">로그인</button>
</form>
);
}
컴포넌트 조합
// 재사용 가능한 버튼 컴포넌트
function Button({ onClick, children, variant = 'primary' }) {
const styles = {
primary: { backgroundColor: 'blue', color: 'white' },
secondary: { backgroundColor: 'gray', color: 'white' },
danger: { backgroundColor: 'red', color: 'white' }
};
return (
<button onClick={onClick} style={styles[variant]}>
{children}
</button>
);
}
// 사용
function App() {
return (
<div>
<Button onClick={() => alert('저장!')}>
저장
</Button>
<Button onClick={() => alert('취소')} variant="secondary">
취소
</Button>
<Button onClick={() => alert('삭제')} variant="danger">
삭제
</Button>
</div>
);
}
Context API (전역 상태)
import { createContext, useContext, useState } from 'react';
// Context 생성
const ThemeContext = createContext();
// Provider 컴포넌트
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Context 사용
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
backgroundColor: theme === 'light' ? 'white' : 'black',
color: theme === 'light' ? 'black' : 'white'
}}
>
현재 테마: {theme}
</button>
);
}
// App
function App() {
return (
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
}
🤔 자주 묻는 질문
Q1. React를 왜 사용하나요?
A:
// ❌ 바닐라 JavaScript (복잡함)
const button = document.createElement('button');
button.textContent = '클릭';
button.addEventListener('click', () => {
const p = document.querySelector('#count');
const count = parseInt(p.textContent);
p.textContent = count + 1;
});
document.body.appendChild(button);
// ✅ React (간단함)
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>클릭: {count}</button>;
}
// React의 장점
1. 선언적 (무엇을 보여줄지만 작성)
2. 컴포넌트 재사용
3. Virtual DOM으로 성능 최적화
4. 거대한 생태계 (라이브러리, 도구)
5. React Native로 모바일 앱도 개발 가능
Q2. JSX란 무엇인가요?
A: JavaScript + XML의 합성어로, HTML 같은 문법으로 React 엘리먼트를 작성합니다:
// JSX (개발자가 작성)
const element = <h1 className="title">Hello</h1>;
// 변환 후 (Babel이 자동 변환)
const element = React.createElement(
'h1',
{ className: 'title' },
'Hello'
);
// JSX 규칙
// 1. 반드시 하나의 루트 엘리먼트
// ❌ 잘못된 예
return (
<h1>Title</h1>
<p>Content</p>
);
// ✅ 올바른 예
return (
<div>
<h1>Title</h1>
<p>Content</p>
</div>
);
// 또는 Fragment 사용
return (
<>
<h1>Title</h1>
<p>Content</p>
</>
);
// 2. className (class 대신)
<div className="container">
// 3. camelCase 속성
<div onClick={handler} onMouseEnter={handler}>
// 4. JavaScript 표현식은 {}로
<h1>1 + 1 = {1 + 1}</h1>
<img src={user.avatar} />
Q3. Hook이란?
A: 함수형 컴포넌트에서 state와 생명주기를 사용할 수 있게 해주는 함수입니다:
import { useState, useEffect, useContext, useRef, useMemo, useCallback } from 'react';
// 1. useState - 상태 관리
const [state, setState] = useState(initialValue);
// 2. useEffect - 부수 효과 (API 호출, 구독 등)
useEffect(() => {
// 실행할 코드
return () => {
// 클린업
};
}, [dependencies]);
// 3. useContext - Context 접근
const value = useContext(MyContext);
// 4. useRef - DOM 접근, 값 유지
const inputRef = useRef(null);
<input ref={inputRef} />
// 5. useMemo - 비용 큰 계산 캐싱
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
// 6. useCallback - 함수 캐싱
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// Hook 규칙
// 1. 최상위에서만 호출 (반복문, 조건문 X)
// 2. React 함수 내에서만 호출
Q4. 클래스형 vs 함수형 컴포넌트?
A: 현재는 함수형 컴포넌트가 표준입니다:
// 클래스형 (레거시)
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
console.log('마운트됨');
}
render() {
return (
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
{this.state.count}
</button>
);
}
}
// 함수형 (현대, 권장)
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('마운트됨');
}, []);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
// 함수형의 장점
- 코드가 짧고 간결
- Hook 사용 가능
- this 바인딩 불필요
- 테스트 쉬움
Q5. React 성능 최적화는?
A:
// 1. React.memo - 컴포넌트 메모이제이션
const MemoizedComponent = React.memo(function MyComponent(props) {
return <div>{props.value}</div>;
});
// 2. useMemo - 값 캐싱
function Component({ items }) {
// items가 변경될 때만 재계산
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.price, 0);
}, [items]);
return <div>Total: {total}</div>;
}
// 3. useCallback - 함수 캐싱
function Parent() {
const [count, setCount] = useState(0);
// count가 변경될 때만 새 함수 생성
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return <Child onClick={handleClick} />;
}
// 4. 리스트 key 최적화
// ❌ 인덱스 사용 (비효율)
{items.map((item, index) => <Item key={index} {...item} />)}
// ✅ 고유 ID 사용
{items.map(item => <Item key={item.id} {...item} />)}
// 5. 코드 스플리팅 (lazy loading)
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
<Suspense fallback={<div>로딩 중...</div>}>
<LazyComponent />
</Suspense>
);
}
🎓 다음 단계
React를 이해했다면, 다음을 학습해보세요:
- Node.js란? - 백엔드와 연동
- 웹 성능 최적화 (문서 작성 예정) - React 앱 최적화
- SEO 기초 (문서 작성 예정) - Next.js로 SEO 개선
React 생태계
// 상태 관리
- Redux: 전역 상태 관리
- Zustand: 간단한 상태 관리
- Recoil: Facebook의 상태 관리
// 라우팅
- React Router: SPA 라우팅
// 스타일링
- styled-components: CSS-in-JS
- Tailwind CSS: 유틸리티 CSS
- Material-UI: 컴포넌트 라이브러리
// 프레임워크
- Next.js: SSR, SSG
- Gatsby: 정적 사이트 생성
// 테스팅
- Jest: 테스트 프레임워크
- React Testing Library: 컴포넌트 테스트
🎬 마무리
React는 현대 웹 개발의 핵심 라이브러리입니다:
- 컴포넌트: 재사용 가능한 UI 블록
- Virtual DOM: 효율적인 업데이트
- JSX: 직관적인 문법
- Hook: 강력한 함수형 컴포넌트
React로 인터랙티브하고 성능 좋은 웹 애플리케이션을 만들어보세요! ⚛️✨