1. React란?
React는 2013년 Facebook이 오픈소스로 공개한 UI 라이브러리입니다. Virtual DOM을 통한 성능 최적화와 컴포넌트 기반 아키텍처로 웹 개발의 패러다임을 바꿨습니다.
2. 개발 환경 구축 (Node.js & Vite & VS Code)
과거에는 React 프로젝트를 세팅하기 위해 무겁고 느린 CRA(Create React App) 방식을 주로 사용했으나, 오늘날의 업계 표준이자 사실상의 정석은 초고속 번들러 도구인 Vite(바이트)를 사용하는 것입니다.
2.1 사전 준비: Node.js 설치
React 애플리케이션을 빌드하고 외부 패키지를 관리하기 위해서는 Node.js와 npm이 로컬 컴퓨터에 필수적으로 셋업되어 있어야 합니다. 아직 설치가 되지 않았다면 JavaScript 가이드의 Node.js 설치 과정을 참고하여 우선 셋업해 주세요.
2.2 Vite를 이용한 React 프로젝트 구성
- 프로젝트 템플릿 생성: 터미널 또는 명령 프롬프트를 열고 원하는 상위 폴더로 이동한 뒤 아래 명령어를 작성합니다.
# React (JavaScript 기반) 생성 npm create vite@latest my-react-app -- --template react # React (TypeScript 기반) 생성 npm create vite@latest my-react-app -- --template react-tsbash - 프로젝트 디렉토리 이동 및 의존성 다운로드:
cd my-react-app npm installbash - 로컬 개발 서버 실행: 설치가 완료되면 아래 명령어로 즉시 핫 리로딩이 적용되는 로컬 개발 서버를 작동시킵니다.
npm run devbash(서버 실행 후 터미널 창에 뜨는
http://localhost:5173링크를 클릭하여 웹브라우저에서 React 웰컴 페이지가 올바르게 로드되는지 확인합니다.)
2.3 VS Code 및 브라우저 디버그 환경 최적화
- VS Code Snippet 확장 추천:
VS Code 마켓플레이스에서 ES7+ React/Redux/React-Native snippets 확장을 검색하여 설치합니다. 새 파일에서
rafce를 입력하고 탭을 누르면 아래와 같은 기본적인 리액트 화살표 컴포넌트 틀이 즉시 생성되어 개발 속도를 획기적으로 상승시켜 줍니다.import React from 'react' const MyComponent = () => { return ( <div>MyComponent</div> ) } export default MyComponentjavascript - React Developer Tools 설치:
크롬(Chrome) 브라우저를 사용할 경우, React Developer Tools 확장 기능을 웹 스토어에서 검색하여 설치하는 것을 강력히 권장합니다. 브라우저 F12 개발자 도구 내에 **Components**와 **Profiler** 탭이 생성되어 실시간으로 Props, State 관계를 완벽히 모니터링할 수 있습니다.
3. JSX (JavaScript XML)
JSX는 JavaScript를 확장한 문법으로, JavaScript 파일 내부에서 HTML과 유사한 마크업을 직접 직관적으로 작성할 수 있도록 지원합니다. 중괄호({})를 이용해 동적인 값을 유연하게 템플릿에 매핑합니다.
const name = 'TestForge';
// HTML 요소와 변수를 중괄호로 동적 바인딩
const element = <h1>Hello, {name}</h1>;jsx
4. 컴포넌트 (Components)
React 애플리케이션은 독립적이고 재사용 가능한 컴포넌트 조각들로 구성됩니다. 현대 React의 정석은 JS 함수 형식으로 정의하는 함수형 컴포넌트(Functional Component)입니다.
// 첫 글자는 반드시 대문자로 시작해야 합니다.
function Welcome() {
return (
<div className="welcome-card">
<h2>TestForge 에 오신 것을 환영합니다!</h2>
<p>컴포넌트를 통해 UI를 쉽게 분할 조립할 수 있습니다.</p>
</div>
);
}jsx
5. Props (Properties)
Props는 부모 컴포넌트가 자식 컴포넌트에게 전달하는 읽기 전용(Read-only) 데이터입니다. 외부 옵션이나 데이터를 컴포넌트 내부에 주입할 때 사용됩니다.
// 1. 자식 컴포넌트 (props 인자를 구조분해할당으로 받음)
function UserCard({ username, role }) {
return (
<div className="user-card">
<h3>이름: {username}</h3>
<p>역할: {role}</p>
</div>
);
}
// 2. 부모 컴포넌트에서 속성 넘겨주기
function App() {
return <UserCard username="철수" role="Developer" />;
}jsx
6. State (useState)
State는 컴포넌트 내부에서 상태값을 가지고 있어 변경 시 화면을 자동으로 다시 렌더링(Re-rendering)하도록 유도하는 객체입니다. useState 훅(Hook)을 통해 세팅합니다.
import { useState } from 'react';
function Counter() {
// [현재값, 변경용 세터함수] = useState(초기값)
const [count, setCount] = useState(0);
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>
1 증가
</button>
</div>
);
}jsx
7. 이벤트 처리 (Event Handling)
React 요소에서의 이벤트 처리는 소문자 대신 카멜 케이스(camelCase)를 사용하고, 문자열 대신 이벤트 핸들러 함수를 직접 중괄호에 전달합니다.
function ActionButton() {
const handleClick = (e) => {
e.preventDefault(); // 기본 이벤트 막기
alert('동작 버튼이 클릭되었습니다!');
};
return (
<button onClick={handleClick}>
실행하기
</button>
);
}jsx
8. 조건부 렌더링 (Conditional Rendering)
특정 조건의 참/거짓 상태에 따라 원하는 화면 구성만 선택적으로 렌더링할 때 JavaScript 삼항 연산자나 단축 논리 평가(&&)를 템플릿 안에서 유용하게 사용합니다.
function Notification({ isNew, message }) {
return (
<div className="alert">
{/* 1. && 논리곱 연산자 (isNew가 true일 때만 배지 노출) */}
{isNew && <span className="badge">NEW</span>}
{/* 2. 삼항 연산자 조건문 */}
<p>{message ? message : '수신된 메시지가 없습니다.'}</p>
</div>
);
}jsx
9. 리스트 렌더링 (Lists and Keys)
배열 형태의 원소 데이터 세트를 여러 컴포넌트로 펼쳐 렌더링할 때는 JavaScript의 map() 함수를 사용하며, 각 컴포넌트 구분점 보호를 위해 유니크한 key 값을 필수로 부여해야 합니다.
function TodoList() {
const todos = [
{ id: 1, text: 'Vite 개발 환경 구축' },
{ id: 2, text: 'React State 이해하기' },
{ id: 3, text: '배열 데이터 map 렌더링' }
];
return (
<ul>
{/* map() 순회 시 최상단 태그에 key 옵션을 매핑하여 렌더링 최적화 */}
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}jsx
10. useEffect
useEffect는 렌더링 이후 부수 효과(API 호출, 구독, 타이머 등)를 처리합니다. 의존성 배열로 실행 시점을 정밀하게 제어합니다.
import { useState, useEffect, useRef } from 'react';
// ── 의존성 배열 패턴 ──────────────────────────────
useEffect(() => { /* 매 렌더마다 */ });
useEffect(() => { /* 마운트 시 1회 */ }, []);
useEffect(() => { /* userId 변경 시마다 */ }, [userId]);
// ── 실전: 취소 가능한 API 요청 ────────────────────
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false; // cleanup 플래그
setLoading(true);
setError(null);
fetch(`/api/users/${userId}`)
.then(r => { if (!r.ok) throw new Error('Not found'); return r.json(); })
.then(data => { if (!cancelled) setUser(data); })
.catch(err => { if (!cancelled) setError(err.message); })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; }; // cleanup: 언마운트 or userId 변경 시
}, [userId]);
if (loading) return <p>로딩 중...</p>;
if (error) return <p>오류: {error}</p>;
return <div>{user?.name}</div>;
}
// ── useRef: DOM 접근 & 렌더 간 값 유지 ───────────
function Timer() {
const [seconds, setSeconds] = useState(0);
const intervalRef = useRef(null); // 렌더에 영향 없이 값 유지
useEffect(() => {
intervalRef.current = setInterval(() => setSeconds(s => s + 1), 1000);
return () => clearInterval(intervalRef.current);
}, []);
return <div>{seconds}초</div>;
}jsx
11. 커스텀 훅 (Custom Hooks)
로직 재사용의 핵심. use로 시작하는 함수로 상태·효과 로직을 캡슐화합니다.
import { useState, useEffect, useCallback } from 'react';
// ── useFetch: 범용 API 요청 훅 ────────────────────
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!url) return;
let cancelled = false;
setLoading(true);
fetch(url)
.then(r => r.json())
.then(d => { if (!cancelled) setData(d); })
.catch(e => { if (!cancelled) setError(e); })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, [url]);
return { data, loading, error };
}
// ── useLocalStorage: 로컬스토리지 동기화 ──────────
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try { return JSON.parse(localStorage.getItem(key)) ?? initialValue; }
catch { return initialValue; }
});
const set = useCallback(newVal => {
setValue(newVal);
localStorage.setItem(key, JSON.stringify(newVal));
}, [key]);
return [value, set];
}
// ── useDebounce: 검색 입력 최적화 ─────────────────
function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
// 사용 예시
function SearchBox() {
const [query, setQuery] = useLocalStorage('search', '');
const debouncedQuery = useDebounce(query, 400);
const { data, loading } = useFetch(
debouncedQuery ? `/api/search?q=${debouncedQuery}` : null
);
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
{loading && <p>검색 중...</p>}
{data?.results.map(r => <div key={r.id}>{r.title}</div>)}
</>
);
}jsx
12. Context API
Props Drilling 없이 전역 상태를 공유합니다. 작은 규모에는 Context, 대규모 앱에는 Zustand/Redux를 권장합니다.
import { createContext, useContext, useReducer } from 'react';
// 1. 타입 안전한 Context 패턴
const AuthContext = createContext(null);
// 2. useReducer로 복잡한 상태 관리
const authReducer = (state, action) => {
switch (action.type) {
case 'LOGIN': return { ...state, user: action.payload, isAuth: true };
case 'LOGOUT': return { user: null, isAuth: false };
default: return state;
}
};
function AuthProvider({ children }) {
const [state, dispatch] = useReducer(authReducer, { user: null, isAuth: false });
const login = (user) => dispatch({ type: 'LOGIN', payload: user });
const logout = () => dispatch({ type: 'LOGOUT' });
return (
<AuthContext.Provider value={{ ...state, login, logout }}>
{children}
</AuthContext.Provider>
);
}
// 3. 커스텀 훅으로 안전하게 사용
function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
}
// 사용
function UserMenu() {
const { user, isAuth, logout } = useAuth();
if (!isAuth) return <a href="/login">로그인</a>;
return <button onClick={logout}>{user.name} 로그아웃</button>;
}jsx
13. React Router
import {
createBrowserRouter, RouterProvider,
Link, NavLink, Outlet,
useParams, useNavigate, useSearchParams,
} from 'react-router-dom';
// ── 레이아웃 라우트 ──────────────────────────────
function Layout() {
return (
<>
<nav>
<NavLink to="/" end className={({ isActive }) => isActive ? 'active' : ''}>홈</NavLink>
<NavLink to="/blog">블로그</NavLink>
</nav>
<main><Outlet /></main> {/* 자식 라우트 렌더 위치 */}
</>
);
}
// ── 동적 라우트 ───────────────────────────────────
function PostDetail() {
const { id } = useParams(); // /posts/:id
const [params, setParams] = useSearchParams(); // ?page=1
const navigate = useNavigate();
const { data: post, loading } = useFetch(`/api/posts/${id}`);
if (loading) return <p>로딩 중...</p>;
return (
<article>
<h1>{post?.title}</h1>
<button onClick={() => navigate(-1)}>뒤로</button>
<button onClick={() => navigate('/blog')}>목록</button>
</article>
);
}
// ── 라우터 설정 ───────────────────────────────────
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{ index: true, element: <Home /> },
{ path: 'blog', element: <Blog /> },
{ path: 'blog/:id', element: <PostDetail /> },
{ path: '*', element: <NotFound /> },
],
},
]);
export default function App() {
return <RouterProvider router={router} />;
}jsx
14. 다음 단계 (Next Steps)
React의 상태 제어 및 렌더링, 훅스 기법을 마스터하셨다면, 대규모 전역 상공 처리를 위한 **Pinia/Zustand/Redux** 등을 연구하시거나 SSR(Server-side rendering) 프레임워크인 **Next.js** 학습으로 전개해 나가세요!
연계 가이드: ▲ Next.js 완전 가이드 · TypeScript 가이드 · Node.js 가이드