React 입문자를 위한 기초 가이드
"UI를 만드는 가장 인기 있는 방법" — React를 처음 접하는 분들을 위한 친절한 입문서입니다.
1. React란 무엇인가?
React는 Facebook(현 Meta)이 만든 JavaScript UI 라이브러리입니다. 웹 애플리케이션의 화면(View)을 만드는 데 특화되어 있으며, 전 세계에서 가장 널리 사용되는 프론트엔드 기술 중 하나입니다.
React의 핵심 철학
컴포넌트 기반(Component-Based)
화면을 독립적인 조각(컴포넌트)으로 나누어 개발합니다. 레고 블록처럼 각각의 컴포넌트를 조합해 복잡한 UI를 만들 수 있습니다.
선언형(Declarative)
"어떻게 화면을 그릴지"가 아니라 "화면이 어떤 상태여야 하는지"를 선언합니다. 데이터가 바뀌면 React가 알아서 화면을 업데이트해 줍니다.
Virtual DOM
React는 실제 DOM을 직접 조작하지 않고, 메모리 안의 가상 DOM(Virtual DOM)을 먼저 업데이트한 뒤 실제 변경 사항만 효율적으로 반영합니다. 덕분에 성능이 뛰어납니다.
2. 개발 환경 설정
필수 준비물
- Node.js (v18 이상 권장) — nodejs.org에서 다운로드
- 코드 에디터 — VS Code 추천
- 터미널 사용법 기초
프로젝트 생성
가장 빠르게 React 프로젝트를 시작하는 방법은 Vite를 사용하는 것입니다.
# Vite로 React 프로젝트 생성
npm create vite@latest my-react-app -- --template react
# 프로젝트 폴더로 이동
cd my-react-app
# 패키지 설치
npm install
# 개발 서버 실행
npm run dev
브라우저에서 http://localhost:5173을 열면 React 앱이 실행됩니다! 🎉
프로젝트 구조 살펴보기
my-react-app/
├── public/ # 정적 파일 (이미지, 폰트 등)
├── src/
│ ├── App.jsx # 루트 컴포넌트
│ ├── App.css # 스타일
│ └── main.jsx # 앱의 진입점
├── index.html # HTML 템플릿
└── package.json # 프로젝트 설정
3. 첫 번째 컴포넌트 만들기
React에서 컴포넌트는 UI의 기본 단위입니다. 함수 형태로 작성하며, JSX를 반환합니다.
// src/components/Hello.jsx
function Hello() {
return (
<div>
<h1>안녕하세요, React!</h1>
<p>첫 번째 컴포넌트를 만들었습니다.</p>
</div>
);
}
export default Hello;
이 컴포넌트를 App.jsx에서 사용해봅시다.
// src/App.jsx
import Hello from './components/Hello';
function App() {
return (
<div>
<Hello />
</div>
);
}
export default App;
컴포넌트 작성 규칙
- 컴포넌트 이름은 반드시 대문자로 시작합니다 (
Hello,MyButton등) - 반드시 하나의 루트 요소를 반환해야 합니다
- 여러 요소를 감싸고 싶을 때는
<>...</>(Fragment)를 사용합니다
// Fragment 사용 예시
function MyComponent() {
return (
<>
<h1>제목</h1>
<p>내용</p>
</>
);
}
4. JSX 이해하기
JSX는 JavaScript 안에서 HTML처럼 UI를 작성할 수 있게 해주는 문법 확장입니다. 실제로는 JavaScript로 변환되어 실행됩니다.
JSX의 핵심 규칙
중괄호 {}로 JavaScript 표현식 삽입
const name = '철수';
const age = 25;
function Profile() {
return (
<div>
<p>이름: {name}</p>
<p>나이: {age}</p>
<p>10년 후: {age + 10}살</p>
</div>
);
}
HTML 속성은 camelCase로 작성
// HTML → JSX
// class → className
// for → htmlFor
// onclick → onClick
// background-color → backgroundColor
<div className="container">
<label htmlFor="input">이름</label>
<input id="input" type="text" />
</div>
자기 닫힘 태그(Self-closing tag) 필수
// ✅ 올바른 JSX
<img src="photo.jpg" alt="사진" />
<input type="text" />
<br />
// ❌ 틀린 JSX
<img src="photo.jpg">
<input type="text">
5. Props: 컴포넌트에 데이터 전달하기
Props(Properties)는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방법입니다.
// 자식 컴포넌트: props를 받아 사용
function UserCard({ name, age, job }) {
return (
<div className="card">
<h2>{name}</h2>
<p>나이: {age}세</p>
<p>직업: {job}</p>
</div>
);
}
// 부모 컴포넌트: props를 전달
function App() {
return (
<div>
<UserCard name="김철수" age={28} job="개발자" />
<UserCard name="이영희" age={24} job="디자이너" />
</div>
);
}
주의사항: Props는 읽기 전용
Props는 자식 컴포넌트에서 직접 수정할 수 없습니다. 데이터의 흐름은 항상 부모 → 자식 방향입니다.
// ❌ 잘못된 사용 — props 직접 수정 금지
function BadComponent({ name }) {
name = '홍길동'; // 이렇게 하면 안 됩니다!
return <p>{name}</p>;
}
6. State: 변하는 데이터 관리하기
State는 컴포넌트 내부에서 관리하는 데이터입니다. State가 변하면 React가 자동으로 화면을 다시 렌더링합니다.
useState Hook을 사용해 State를 만듭니다.
import { useState } from 'react';
function Counter() {
// [현재 값, 값을 바꾸는 함수] = useState(초기값)
const [count, setCount] = useState(0);
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>+1 증가</button>
<button onClick={() => setCount(count - 1)}>-1 감소</button>
<button onClick={() => setCount(0)}>초기화</button>
</div>
);
}
여러 개의 State 사용하기
import { useState } from 'react';
function LoginForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
return (
<form>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="아이디"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="비밀번호"
/>
<p>입력 중인 아이디: {username}</p>
</form>
);
}
⚠️ 중요: State는 직접 수정하지 않는다
const [count, setCount] = useState(0);
// ❌ 잘못된 방법 — 직접 수정
count = count + 1;
// ✅ 올바른 방법 — setter 함수 사용
setCount(count + 1);
7. 이벤트 처리하기
React에서 이벤트는 HTML과 비슷하지만 몇 가지 차이가 있습니다.
function ButtonExample() {
// 이벤트 핸들러 함수 정의
const handleClick = () => {
alert('버튼을 클릭했습니다!');
};
const handleMouseEnter = () => {
console.log('마우스가 올라왔습니다');
};
return (
<div>
{/* onClick에 함수를 전달 (호출하지 않음) */}
<button onClick={handleClick}>클릭하세요</button>
{/* 인라인 화살표 함수 */}
<button onClick={() => alert('안녕!')}>인라인</button>
{/* 다양한 이벤트 */}
<div onMouseEnter={handleMouseEnter}>마우스를 올려보세요</div>
</div>
);
}
자주 사용하는 이벤트
| 이벤트 | 설명 |
|---|---|
onClick |
클릭 |
onChange |
입력값 변경 |
onSubmit |
폼 제출 |
onKeyDown / onKeyUp |
키보드 입력 |
onMouseEnter / onMouseLeave |
마우스 진입/이탈 |
onFocus / onBlur |
포커스 얻음/잃음 |
8. 조건부 렌더링
데이터에 따라 다른 UI를 보여주고 싶을 때 조건부 렌더링을 사용합니다.
방법 1: if문 사용
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <h1>돌아오셨군요!</h1>;
}
return <h1>먼저 로그인해주세요.</h1>;
}
방법 2: 삼항 연산자 (JSX 안에서 사용)
function UserStatus({ isOnline }) {
return (
<div>
<span style={{ color: isOnline ? 'green' : 'gray' }}>
{isOnline ? '🟢 온라인' : '⚫ 오프라인'}
</span>
</div>
);
}
방법 3: && 연산자 (조건이 참일 때만 표시)
function Notification({ hasMessage, messageCount }) {
return (
<div>
<h1>받은 편지함</h1>
{hasMessage && (
<p>읽지 않은 메시지가 {messageCount}개 있습니다.</p>
)}
</div>
);
}
9. 리스트 렌더링
배열 데이터를 화면에 나열할 때는 map() 메서드를 사용합니다.
function TodoList() {
const todos = [
{ id: 1, text: 'React 공부하기', done: true },
{ id: 2, text: '컴포넌트 만들기', done: false },
{ id: 3, text: '프로젝트 완성하기', done: false },
];
return (
<ul>
{todos.map((todo) => (
<li
key={todo.id}
style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
>
{todo.text}
</li>
))}
</ul>
);
}
⚠️ key prop은 필수
리스트를 렌더링할 때는 반드시 각 항목에 고유한 key 를 부여해야 합니다. React가 어떤 항목이 변경되었는지 효율적으로 파악하기 위해 사용합니다.
// ✅ 좋은 예: 고유한 id 사용
{items.map((item) => <li key={item.id}>{item.name}</li>)}
// ⚠️ 권장하지 않음: 인덱스 사용 (순서가 바뀌면 문제 발생 가능)
{items.map((item, index) => <li key={index}>{item.name}</li>)}
10. useEffect: 사이드 이펙트 다루기
useEffect는 컴포넌트가 렌더링된 후 실행할 작업을 지정하는 Hook입니다. API 호출, 이벤트 구독, 타이머 설정 등에 사용합니다.
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// userId가 바뀔 때마다 실행
setLoading(true);
fetch(`https://api.example.com/users/${userId}`)
.then((res) => res.json())
.then((data) => {
setUser(data);
setLoading(false);
});
}, [userId]); // 의존성 배열: userId가 바뀔 때만 실행
if (loading) return <p>로딩 중...</p>;
if (!user) return <p>사용자를 찾을 수 없습니다.</p>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
의존성 배열(Dependency Array) 이해하기
// 1. 렌더링될 때마다 실행
useEffect(() => {
console.log('매번 실행');
});
// 2. 처음 한 번만 실행 (컴포넌트 마운트 시)
useEffect(() => {
console.log('처음 한 번만 실행');
}, []);
// 3. value가 변경될 때마다 실행
useEffect(() => {
console.log('value가 바뀔 때 실행:', value);
}, [value]);
클린업(Cleanup) 함수
컴포넌트가 사라질 때 정리 작업이 필요하다면 return으로 함수를 반환합니다.
useEffect(() => {
const timer = setInterval(() => {
console.log('1초마다 실행');
}, 1000);
// 컴포넌트가 사라질 때 타이머 정리
return () => clearInterval(timer);
}, []);
11. 다음 단계로 나아가기
React 기초를 익혔다면 다음 주제들을 공부해보세요!
🔑 중요 Hook들
| Hook | 용도 |
|---|---|
useContext |
전역 데이터 공유 |
useReducer |
복잡한 상태 관리 |
useRef |
DOM 직접 접근 |
useMemo |
계산 결과 캐싱 |
useCallback |
함수 캐싱 |
📚 함께 배우면 좋은 기술
- React Router — 페이지 이동(라우팅) 처리
- Zustand 또는 Redux — 전역 상태 관리
- React Query (TanStack Query) — 서버 데이터 관리
- TypeScript — 타입 안전성 추가
- Next.js — 서버 사이드 렌더링(SSR) 프레임워크
🌐 공식 학습 자료
- React 공식 문서 — 한국어 지원, 최고의 레퍼런스
- React 공식 튜토리얼 — 틱택토 게임 만들기
- Vite 공식 문서 — 빠른 개발 환경
마치며
Zustand + TanStack Query + React Hook Form + Zod + Tailwind + shadcn/ui