React 개발 핵심 개념 및 라이브러리 정리
1. 라우터 (React Router Dom)
SPA(Single Page Application) 환경에서는 페이지가 새로고침 되지 않고, URL에 따라 보여주는 컴포넌트만 교체되어야 한다. 이를 구현하기 위해 React-Router-Dom 라이브러리를 사용한다. 개발자가 직관적으로 URL과 화면(컴포넌트)을 일치시키는 작업을 돕는다.
1.1 설치
npm을 통해 라이브러리를 설치한다. TypeScript 환경에서는 타입 정의가 포함된 패키지나 @types 패키지가 필요할 수 있으나, 최신 버전의 react-router-dom은 자체적으로 타입을 지원하는 경우가 많다. 만약 타입 에러가 발생한다면 아래와 같이 설치한다.
npm install react-router-dom
npm install -D @types/react-router-dom
1.2 사용 구조 (createBrowserRouter)
최신 리액트 라우터(v6.4 이상)에서는 createBrowserRouter 함수를 사용하여 라우팅 객체를 생성하고, RouterProvider를 통해 애플리케이션에 주입하는 방식을 권장한다. 이 방식은 데이터 로딩(Loader), 폼 처리(Action) 등의 기능을 활용하기에 적합하다.
예시 코드 (main.tsx 또는 App.tsx):
import React from 'react';
import ReactDOM from 'react-dom/client';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Layout from './components/layout/Layout';
import Home from './pages/Home';
import Login from './pages/Login';
import NotFound from './pages/NotFound';
// 라우터 객체 생성
const router = createBrowserRouter([
{
path: "/",
element: <Layout />, // 공통 레이아웃 (헤더, 푸터 등)
errorElement: <NotFound />, // 에러 발생 시 보여줄 페이지
children: [
{
index: true, // path: "/" 와 동일
element: <Home />
},
{
path: "login",
element: <Login />
}
]
}
]);
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
2. Model 폴더 (TypeScript Interface)
TypeScript를 사용하는 가장 큰 이유는 타입 안정성이다. 서버와 통신할 때 프론트엔드는 어떤 데이터가 들어오고 나가는지 명확히 알아야 한다. 이를 위해 model 폴더를 생성하여 인터페이스(Interface)나 타입(Type)을 미리 정의해 둔다.
2.1 필요성
JS에서는 데이터가 런타임에 결정되므로, 서버 응답값에 오타가 있거나 누락되어도 실행 전까지 알기 어렵다. 하지만 인터페이스를 미리 만들어두면 코드 작성 시 자동 완성이 지원되고, 컴파일 단계에서 오류를 잡아낼 수 있어 유지보수성이 크게 향상된다.
예시 코드 (src/models/user.model.ts):
export interface User {
id: number;
email: string;
password?: string; // 선택적 속성 (서버 응답에는 없을 수도 있음)
username: string;
role: 'USER' | 'ADMIN'; // 리터럴 타입을 통한 값 제한
}
3. 서버와 통신 (Axios)
프론트엔드에서 서버와 데이터를 주고받기 위해서는 HTTP 통신이 필요하다. Ajax, XMLHttpRequest, fetch API 등 다양한 방법이 있지만, 실무에서는 편의성과 기능이 강력한 Axios 라이브러리를 주로 사용한다.
3.1 Axios의 장점 (vs fetch)
- 자동 JSON 변환: 응답 데이터를 자동으로 JSON으로 파싱해준다.
- 인터셉터(Interceptors): 요청이나 응답을 가로채서 공통된 로직(토큰 주입, 에러 처리 등)을 수행하기 쉽다.
- 브라우저 호환성: 구형 브라우저에서도 비교적 안정적으로 동작한다.
3.2 Axios 설치
npm install axios
3.3 Axios 사용 예시 코드
일반적으로 axios.create를 사용해 인스턴스를 만들어 재사용하는 것이 관례이다.
// src/api/http.ts (인스턴스 생성)
import axios, { AxiosRequestConfig } from "axios";
const BASE_URL = "http://localhost:8080";
const DEFAULT_TIMEOUT = 30000;
export const createClient = (config?: AxiosRequestConfig) => {
const axiosInstance = axios.create({
baseURL: BASE_URL,
timeout: DEFAULT_TIMEOUT,
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
...config,
});
// 요청 인터셉터 (예: 토큰 자동 주입)
axiosInstance.interceptors.request.use((config) => {
const token = localStorage.getItem("token");
if(token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, (error) => Promise.reject(error));
return axiosInstance;
};
export const httpClient = createClient();
// 사용 예시 (API 호출)
import { httpClient } from "./http";
import { User } from "../models/user.model";
export const fetchUserData = async () => {
const response = await httpClient.get<User>("/users/me");
return response.data; // User 타입으로 추론됨
}
4. CORS 에러 (Cross-Origin Resource Sharing)
개발 단계에서 백엔드 API를 호출할 때 가장 흔하게 마주치는 에러이다.
4.1 CORS란 무엇인가?
CORS(Cross-Origin Resource Sharing)는 브라우저의 보안 정책인 SOP(Same-Origin Policy, 동일 출처 정책)를 완화하기 위한 표준이다. 브라우저는 보안상의 이유로 스크립트 내에서 초기화되는 교차 출처(다른 도메인, 포트, 프로토콜) HTTP 요청을 제한한다. 즉, localhost:3000(프론트)에서 localhost:8080(백엔드)으로 요청을 보내면 출처(Origin)가 다르기 때문에 브라우저가 이를 차단하는 것이다.
4.2 CORS 에러 해결 방법
- 서버(Backend) 설정: 백엔드 서버에서 응답 헤더(
Access-Control-Allow-Origin)에 프론트엔드의 도메인을 허용하도록 설정한다. (가장 정석적인 방법) - 프록시(Proxy) 설정 (개발 환경): Vite나 CRA 등 프론트엔드 개발 서버 설정을 통해 요청을 우회한다. 브라우저는 같은 출처로 인식하게 하여 CORS를 회피한다.
Vite 환경에서의 Proxy 설정 예시 (vite.config.ts):
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: 'http://localhost:8080', // 백엔드 주소
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
5. React Hook Form
회원가입이나 로그인 같은 폼(Form)을 다룰 때, useState를 하나하나 만들어 onChange 이벤트를 걸고 유효성 검사를 수행하는 과정은 매우 번거롭고 코드를 복잡하게 만든다. 또한 입력할 때마다 리렌더링이 발생하는 성능 이슈도 있다.
이를 해결하기 위해 React Hook Form을 사용한다. 비제어 컴포넌트(Uncontrolled Component) 방식을 활용하여 리렌더링을 최소화하고, 직관적인 API를 제공한다.
5.1 주요 기능
- register: input 요소를 hook form에 등록하고 유효성 검사 규칙(
required,minLength등)을 설정한다. - handleSubmit: 폼 제출 시 유효성 검사를 통과하면 데이터를 처리하는 함수를 실행한다.
- formState: 폼의 현재 상태(에러 발생 여부, 터치 여부 등)를 제공한다.
5.2 설치
npm install react-hook-form
5.3 React Hook Form 예시 코드
import React from 'react';
import { useForm, SubmitHandler } from "react-hook-form";
import { User } from '../models/user.model';
interface JoinFormInputs extends Pick<User, 'email' | 'password'> {
passwordConfirm: string;
username: string;
}
const JoinPage = () => {
const {
register,
handleSubmit,
watch,
formState: { errors }
} = useForm<JoinFormInputs>();
// 폼 제출 시 실행될 함수
const onSubmit: SubmitHandler<JoinFormInputs> = (data) => {
console.log("제출된 데이터:", data);
// 여기서 회원가입 API 호출
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* 이메일 입력 */}
<div>
<label>Email</label>
<input
{...register("email", {
required: "이메일은 필수입니다.",
pattern: {
value: /^\S+@\S+$/i,
message: "이메일 형식이 올바르지 않습니다."
}
})}
/>
{errors.email && <span className="error">{errors.email.message}</span>}
</div>
{/* 비밀번호 입력 */}
<div>
<label>Password</label>
<input
type="password"
{...register("password", { required: "비밀번호를 입력해주세요." })}
/>
{errors.password && <span>{errors.password.message}</span>}
</div>
{/* 제출 버튼 */}
<button type="submit">회원가입</button>
</form>
);
};
export default JoinPage;'Programmers' 카테고리의 다른 글
| [56일차]Optimistic Updates, Styled Components 고급 활용 (0) | 2025.11.28 |
|---|---|
| [55일차]Zustand와 TypeScript를 활용한 상태 관리 (0) | 2025.11.27 |
| [53일차]컴포넌트 테스트와 forwardRef 정리 (0) | 2025.11.24 |
| [51일차]React 프로젝트 생성: Create React App vs Vite (0) | 2025.11.21 |
| [47일차]React 상태 관리 및 개발 환경 분석 (0) | 2025.11.17 |