React 개요
React란?
React는 페이스북(현재 Meta)에서 개발한 사용자 인터페이스(UI)를 구축하기 위한 JavaScript 라이브러리입니다. 컴포넌트 기반 아키텍처와 가상 DOM을 활용하여 효율적이고 유지보수 가능한 웹 애플리케이션을 만들 수 있도록 도와줍니다. 단일 페이지 애플리케이션(SPA) 개발에 널리 사용되며, 가장 인기 있는 프론트엔드 라이브러리입니다.
목차
React의 역사
1. 초기 개발 (2011-2013)
- 2011년: Facebook의 Jordan Walke가 React 개발 시작
- 배경: Facebook의 뉴스피드 성능 개선 필요
- 2013년 5월: React가 오픈소스로 공개
- 초기 반응: JSX 문법에 대한 논란, 학습 곡선 우려
2. React 0.x 시대 (2013-2015)
- 특징:
- 컴포넌트 기반 아키텍처
- 가상 DOM 개념 도입
- 단방향 데이터 흐름
- 주요 개념: Props, State, 생명주기 메서드
- 문제점: 복잡한 설정, 빌드 도구 필요
3. React 15 (2016년)
- 2016년 4월: React 15.0 릴리즈
- 주요 개선:
- 새로운 재조정 알고리즘
- 에러 경계 (Error Boundaries) 개념 도입
- 불필요한
<div>래퍼 제거 가능
- 성능: 이전 버전 대비 50% 이상 성능 향상
4. React 16 (2017년) - “Fiber”
- 2017년 9월: React 16.0 릴리즈
- 주요 변화:
- Fiber 아키텍처: 새로운 재조정 알고리즘
- Fragment: 불필요한 DOM 요소 제거
- Portal: DOM 트리 외부 렌더링
- Error Boundary: 컴포넌트 트리의 에러 처리
- 서버 사이드 렌더링: 완전한 SSR 지원
- 성능: 큰 리스트 렌더링 성능 대폭 개선
5. React Hooks (2019년)
- 2019년 2월: React 16.8 릴리즈
- 혁신적 변화:
- Hooks 도입: 함수형 컴포넌트에서 상태 관리 가능
useState,useEffect등 주요 Hooks 제공- 클래스 컴포넌트 대신 함수형 컴포넌트 사용 권장
- 영향: React 개발 패러다임 전환
6. React 17 (2020년)
- 2020년 10월: React 17.0 릴리즈
- 특징: “No New Features”
- 주요 변경:
- 새로운 JSX Transform
- 이벤트 위임 방식 변경
- 점진적 업그레이드 지원
- 목적: 미래 버전을 위한 기반 마련
7. React 18 (2022년)
- 2022년 3월: React 18.0 릴리즈
- 주요 기능:
- 동시성 (Concurrency): 우선순위 기반 렌더링
- 자동 배칭: 상태 업데이트 자동 그룹화
- Suspense 개선: 서버 컴포넌트 지원 준비
- Transitions: 긴급하지 않은 업데이트 지연
- Hydration: 서버 렌더링된 콘텐츠 개선
- 성능: 더 나은 사용자 경험 제공
8. React 19 (2024년)
- 예상 기능:
- React Compiler (자동 최적화)
- 서버 컴포넌트 정식 지원
- Actions (폼 처리 개선)
- 더 나은 Hydration
생태계 발전
- 2015년: Redux 등장 (상태 관리)
- 2016년: Create React App (CRA) 출시
- 2017년: Next.js 인기 상승 (SSR 프레임워크)
- 2018년: React Native 모바일 개발 확산
- 2020년: Vite, Remix 등 새로운 도구 등장
장단점
장점
1. 컴포넌트 기반 아키텍처
- 재사용성: 컴포넌트를 여러 곳에서 재사용 가능
- 모듈화: 작은 단위로 코드 분리 가능
- 유지보수성: 컴포넌트별로 독립적 수정 가능
- 테스트 용이: 컴포넌트 단위 테스트 쉬움
2. 가상 DOM (Virtual DOM)
- 성능 최적화: 실제 DOM 조작 최소화
- 효율적 업데이트: 변경된 부분만 업데이트
- 크로스 브라우저 호환: 브라우저 차이 추상화
- 예측 가능한 렌더링: 선언적 UI 작성
3. 단방향 데이터 흐름
- 예측 가능성: 데이터 흐름이 명확함
- 디버깅 용이: 문제 추적이 쉬움
- 데이터 일관성: 단일 소스의 진실 (Single Source of Truth)
- 버그 예방: 데이터 흐름이 일정하여 실수 감소
4. 풍부한 생태계
- 거대한 커뮤니티: 가장 큰 프론트엔드 커뮤니티
- 풍부한 라이브러리: 거의 모든 기능에 대한 라이브러리 존재
- 지속적인 업데이트: 활발한 개발 및 유지보수
- 학습 자료: 튜토리얼, 블로그, 강의 풍부
5. 유연성
- 프레임워크가 아님: 필요한 부분만 선택적 사용
- 다양한 통합: 다른 라이브러리와 쉽게 통합
- 점진적 도입: 기존 프로젝트에 점진적으로 도입 가능
- 자유로운 아키텍처: 프로젝트 구조 자유롭게 설계
6. 개발 도구
- React DevTools: 강력한 디버깅 도구
- 빠른 개발 환경: Hot Reload 지원
- TypeScript 지원: 타입 안정성 제공
- 다양한 빌드 도구: Webpack, Vite, Parcel 등
7. 직관적인 문법 (JSX)
- HTML과 유사: 기존 웹 개발자에게 친숙
- 표현력: JavaScript 표현식 사용 가능
- 가독성: 컴포넌트 구조가 명확
- IDE 지원: 자동완성, 문법 강조 등
8. Meta의 지속적 지원
- 엔터프라이즈급 지원: 대기업에서 실제 사용
- 장기적 지원: 지속적인 개발 및 개선
- 안정성: 프로덕션 환경에서 검증됨
단점
1. 학습 곡선
- JSX 문법: 초보자에게 생소할 수 있음
- 개념 이해: 컴포넌트, Props, State 개념 필요
- Hooks: 함수형 컴포넌트와 Hooks 학습 필요
- 상태 관리: 복잡한 상태 관리 패턴 이해 필요
2. 빠른 변화
- 업데이트 빈도: 새로운 기능과 패턴이 자주 등장
- 베스트 프랙티스 변화: 권장사항이 자주 변경
- 라이브러리 호환성: 버전 업데이트 시 호환성 문제
- 유지보수 부담: 최신 패턴을 따라가기 어려움
3. 프레임워크가 아님
- 설정 필요: 라우팅, 상태 관리, 빌드 도구 직접 선택
- 의사결정 부담: 많은 선택지 중 결정 필요
- 보일러플레이트: 초기 설정에 시간 소요
- 일관성 부족: 프로젝트마다 구조가 다를 수 있음
4. SEO (검색 엔진 최적화)
- 클라이언트 렌더링: 기본적으로 CSR (Client-Side Rendering)
- 초기 로딩: JavaScript 번들 다운로드 필요
- SSR 필요: SEO가 중요한 경우 Next.js 등 필요
- 추가 설정: SSR 구현을 위한 추가 작업
5. 번들 크기
- 라이브러리 크기: React 자체가 비교적 큼
- 의존성: 많은 라이브러리 사용 시 번들 크기 증가
- 최적화 필요: Code Splitting, Tree Shaking 등 필요
- 초기 로딩 시간: 큰 번들은 느린 초기 로딩
6. 복잡한 상태 관리
- 전역 상태: Redux, Zustand, Jotai 등 필요
- 상태 동기화: 여러 컴포넌트 간 상태 공유 복잡
- 상태 업데이트: 불변성 유지 필요
- 성능 최적화: 메모이제이션, 최적화 전략 필요
7. 과도한 리렌더링
- 불필요한 렌더링: 최적화 없이는 자주 리렌더링
- 성능 이슈: 큰 리스트나 복잡한 컴포넌트에서 성능 저하
- 최적화 필요: React.memo, useMemo, useCallback 등 필요
- 디버깅 어려움: 리렌더링 원인 파악 어려울 수 있음
8. 문서화
- 공식 문서: 좋지만 초보자에게는 복잡할 수 있음
- 버전별 차이: 문서가 버전별로 달라 혼란 가능
- 최신 정보: 커뮤니티 자료가 빠르게 오래될 수 있음
폴더 구조
기본 React 프로젝트 구조 (Create React App)
my-react-app/
├── public/ # 정적 파일
│ ├── index.html # HTML 템플릿
│ ├── favicon.ico
│ └── manifest.json
├── src/ # 소스 코드
│ ├── components/ # 재사용 가능한 컴포넌트
│ │ ├── Button/
│ │ │ ├── Button.jsx
│ │ │ ├── Button.module.css
│ │ │ └── index.js
│ │ ├── Card/
│ │ └── Modal/
│ ├── pages/ # 페이지 컴포넌트
│ │ ├── Home/
│ │ ├── About/
│ │ └── Contact/
│ ├── hooks/ # 커스텀 Hooks
│ │ ├── useAuth.js
│ │ └── useFetch.js
│ ├── context/ # Context API
│ │ └── AuthContext.js
│ ├── services/ # API 서비스
│ │ └── api.js
│ ├── utils/ # 유틸리티 함수
│ │ └── helpers.js
│ ├── assets/ # 이미지, 폰트 등
│ │ ├── images/
│ │ └── fonts/
│ ├── styles/ # 전역 스타일
│ │ ├── global.css
│ │ └── variables.css
│ ├── App.jsx # 루트 컴포넌트
│ ├── App.css
│ ├── index.js # 진입점
│ └── index.css # 전역 CSS
├── package.json
├── README.md
└── .gitignore
기능 기반 폴더 구조 (Feature-based)
src/
├── features/ # 기능별 폴더
│ ├── auth/
│ │ ├── components/
│ │ │ ├── LoginForm.jsx
│ │ │ └── SignupForm.jsx
│ │ ├── hooks/
│ │ │ └── useAuth.js
│ │ ├── services/
│ │ │ └── authService.js
│ │ ├── context/
│ │ │ └── AuthContext.js
│ │ └── pages/
│ │ ├── Login.jsx
│ │ └── Signup.jsx
│ ├── users/
│ │ ├── components/
│ │ │ ├── UserList.jsx
│ │ │ └── UserCard.jsx
│ │ ├── hooks/
│ │ │ └── useUsers.js
│ │ ├── services/
│ │ │ └── userService.js
│ │ └── pages/
│ │ └── Users.jsx
│ └── products/
│ ├── components/
│ ├── hooks/
│ └── services/
├── shared/ # 공유 컴포넌트/유틸
│ ├── components/
│ │ ├── Button/
│ │ ├── Input/
│ │ └── Modal/
│ ├── hooks/
│ └── utils/
├── layouts/ # 레이아웃 컴포넌트
│ ├── MainLayout.jsx
│ └── AuthLayout.jsx
├── routes/ # 라우팅 설정
│ └── index.js
├── store/ # 상태 관리 (Redux 등)
│ ├── slices/
│ ├── store.js
│ └── hooks.js
└── App.jsx
Atomic Design 패턴
src/
├── components/
│ ├── atoms/ # 가장 작은 단위
│ │ ├── Button/
│ │ ├── Input/
│ │ └── Label/
│ ├── molecules/ # atoms 조합
│ │ ├── SearchBox/
│ │ └── FormField/
│ ├── organisms/ # molecules 조합
│ │ ├── Header/
│ │ └── ProductList/
│ ├── templates/ # 레이아웃 구조
│ │ └── PageTemplate/
│ └── pages/ # 완성된 페이지
│ ├── HomePage/
│ └── AboutPage/
Next.js 프로젝트 구조
my-next-app/
├── pages/ # 라우팅 (파일 기반)
│ ├── index.js # / (홈)
│ ├── about.js # /about
│ ├── users/
│ │ ├── index.js # /users
│ │ └── [id].js # /users/:id
│ └── api/ # API 라우트
│ └── users.js
├── components/ # 컴포넌트
│ ├── layout/
│ └── ui/
├── public/ # 정적 파일
├── styles/ # 스타일
├── lib/ # 유틸리티
├── hooks/ # 커스텀 Hooks
└── next.config.js # Next.js 설정
컴포넌트 폴더 구조 (옵션들)
옵션 1: 단일 파일
components/
├── Button.jsx
├── Button.css
└── Button.test.js
옵션 2: 폴더 구조 (권장)
components/
├── Button/
│ ├── Button.jsx
│ ├── Button.module.css
│ ├── Button.test.js
│ └── index.js # export default Button
옵션 3: 세분화된 구조
components/
├── Button/
│ ├── Button.jsx
│ ├── Button.module.css
│ ├── Button.test.js
│ ├── Button.stories.js # Storybook
│ └── index.js
폴더 구조 베스트 프랙티스
1. 컴포넌트는 폴더로 관리
- 각 컴포넌트는 독립적인 폴더
- 관련 파일(스타일, 테스트) 함께 관리
2. 기능 기반 구조 권장
- 기능별로 코드 그룹화
- 관련된 컴포넌트, Hook, 서비스 함께 위치
3. 공유 컴포넌트 분리
- 여러 곳에서 사용하는 컴포넌트는
shared/또는components/ - 기능별 컴포넌트는 각 기능 폴더 내부
4. 절대 경로 사용
import Button from '@/components/Button'jsconfig.json또는tsconfig.json에 설정
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"@/*": ["*"]
}
}
}기본적으로 알아야 할 것
1. 핵심 개념
컴포넌트 (Component)
- 역할: UI를 구성하는 재사용 가능한 단위
- 종류: 함수형 컴포넌트, 클래스 컴포넌트 (구식)
- 특징: Props를 받아서 UI를 반환
// 함수형 컴포넌트 (권장)
function Welcome({ name }) {
return <h1>Hello, {name}!</h1>;
}
// 화살표 함수
const Welcome = ({ name }) => {
return <h1>Hello, {name}!</h1>;
};JSX (JavaScript XML)
- 역할: JavaScript에서 HTML과 유사한 문법 사용
- 특징: JavaScript 표현식 사용 가능, 반드시 하나의 루트 요소
const element = <h1>Hello, World!</h1>;
// 표현식 사용
const name = "React";
const element = <h1>Hello, {name}!</h1>;
// 조건부 렌더링
{isLoggedIn ? <Dashboard /> : <Login />}
// 리스트 렌더링
{items.map(item => <li key={item.id}>{item.name}</li>)}Props (Properties)
- 역할: 부모 컴포넌트에서 자식 컴포넌트로 데이터 전달
- 특징: 읽기 전용, 불변성 유지
// Props 전달
<Button label="Click me" onClick={handleClick} />
// Props 받기
function Button({ label, onClick }) {
return <button onClick={onClick}>{label}</button>;
}State (상태)
- 역할: 컴포넌트 내부의 변경 가능한 데이터
- 특징: State 변경 시 컴포넌트 리렌더링
- Hooks:
useState사용
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}2. 주요 Hooks
useState
- 역할: 컴포넌트에 상태 추가
- 반환값: [현재 상태, 상태 업데이트 함수]
const [state, setState] = useState(초기값);
// 예시
const [name, setName] = useState('');
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: '', age: 0 });
// 함수형 업데이트
setCount(prevCount => prevCount + 1);useEffect
- 역할: 사이드 이펙트 처리 (API 호출, 구독, DOM 조작)
- 의존성 배열: 언제 실행할지 제어
import { useEffect } from 'react';
// 컴포넌트 마운트 시 한 번 실행
useEffect(() => {
console.log('Component mounted');
}, []);
// 특정 값 변경 시 실행
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
// 클린업 함수
useEffect(() => {
const timer = setInterval(() => {
console.log('Tick');
}, 1000);
return () => {
clearInterval(timer); // 컴포넌트 언마운트 시 정리
};
}, []);useContext
- 역할: Context 값을 구독
- 사용: 여러 컴포넌트에서 전역 상태 공유
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Button />
</ThemeContext.Provider>
);
}
function Button() {
const theme = useContext(ThemeContext);
return <button className={theme}>Button</button>;
}useReducer
- 역할: 복잡한 상태 로직 관리
- 사용: 여러 하위 값을 가진 복잡한 상태
import { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}useMemo
- 역할: 계산 결과 메모이제이션
- 사용: 비용이 큰 계산 최적화
import { useMemo } from 'react';
function ExpensiveComponent({ items }) {
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
return <div>{expensiveValue}</div>;
}useCallback
- 역할: 함수 메모이제이션
- 사용: 자식 컴포넌트에 전달하는 함수 최적화
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Clicked');
}, []); // 의존성이 없으면 항상 같은 함수 참조
return <Child onClick={handleClick} />;
}3. 이벤트 처리
function Button() {
const handleClick = (e) => {
e.preventDefault();
console.log('Button clicked');
};
return (
<button onClick={handleClick}>
Click me
</button>
);
}
// 인라인 함수
<button onClick={() => console.log('Clicked')}>
Click
</button>
// 매개변수 전달
<button onClick={(e) => handleClick(id, e)}>
Click
</button>4. 조건부 렌더링
// 삼항 연산자
{isLoggedIn ? <Dashboard /> : <Login />}
// 논리 연산자 (&&)
{isLoading && <Spinner />}
// if 문 (조건부로 반환)
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
// 변수 사용
function Mailbox({ unreadMessages }) {
const unreadCount = unreadMessages.length;
return (
<div>
{unreadCount > 0 && (
<h2>You have {unreadCount} unread messages.</h2>
)}
</div>
);
}5. 리스트 렌더링
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
// key의 중요성
// ❌ 잘못된 예 (인덱스 사용)
{todos.map((todo, index) => (
<li key={index}>{todo.text}</li>
))}
// ✅ 올바른 예 (고유 ID 사용)
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}6. 폼 처리
import { useState } from 'react';
function Form() {
const [formData, setFormData] = useState({
name: '',
email: ''
});
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Name"
/>
<input
name="email"
type="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<button type="submit">Submit</button>
</form>
);
}7. HTTP 요청 (Fetch API)
import { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/users')
.then(response => response.json())
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}8. 커스텀 Hooks
// useFetch.js
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
// 사용
function UserList() {
const { data, loading, error } = useFetch('/api/users');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error</div>;
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}9. Context API (전역 상태 관리)
import { createContext, useContext, useState } from 'react';
const AuthContext = createContext();
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = (userData) => {
setUser(userData);
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
// 사용
function App() {
return (
<AuthProvider>
<Dashboard />
</AuthProvider>
);
}
function Dashboard() {
const { user, logout } = useAuth();
return (
<div>
<p>Welcome, {user.name}!</p>
<button onClick={logout}>Logout</button>
</div>
);
}10. 성능 최적화
React.memo
import { memo } from 'react';
const Button = memo(({ label, onClick }) => {
return <button onClick={onClick}>{label}</button>;
});useMemo와 useCallback
function Parent({ items }) {
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
return <Child value={expensiveValue} onClick={handleClick} />;
}11. 주요 라이브러리
React Router (라우팅)
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<UserDetail />} />
</Routes>
</BrowserRouter>
);
}Axios (HTTP 클라이언트)
import axios from 'axios';
axios.get('/api/users')
.then(response => console.log(response.data))
.catch(error => console.error(error));학습 순서 추천
- 기초: JSX, 컴포넌트, Props
- 상태 관리: useState, 이벤트 처리
- 생명주기: useEffect 기본 사용
- 조건부 렌더링: 삼항 연산자, 논리 연산자
- 리스트 렌더링: map, key
- 폼 처리: 제어 컴포넌트
- 고급 Hooks: useContext, useReducer
- 성능 최적화: React.memo, useMemo, useCallback
- 커스텀 Hooks: 재사용 가능한 로직 추출
- 상태 관리: Context API 또는 Redux
- 라우팅: React Router
- 테스트: React Testing Library
참고 자료
참고
- React는 라이브러리이므로 필요한 도구들을 직접 선택해야 합니다
- 함수형 컴포넌트와 Hooks를 사용하는 것이 현재 권장 방식입니다
- 성능 최적화는 필요할 때만 적용하세요 (과도한 최적화는 금물)
- 커뮤니티가 크므로 문제 해결이 상대적으로 쉽습니다
- 지속적으로 업데이트되므로 최신 문서를 확인하세요