유틸 함수 모듈화
1. 금액을 한국 통화로 포맷팅
export const formmatCurrency = number => number.toLocaleString() + '원'
2. 날짜를 한국 시간 형식으로 포맷팅
export const formatDate = date => {
const d = new Date(date)
const year = d.getFullYear()
// getMonth()는 0부터 시작하므로 1을 더하고, 10보다 작으면 앞에 0 추가
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${year}. ${month}. ${day}`
}
3. 디바운스
// 디바운스 : 연속된 호출을 지연시켜 한번만 실행. 함수(함수, 대시시간)
export const debounce = (func, delay = 300) => {
let timerId
return function (...args) {
if (timerId) clearTimeout(timerId)
timerId = setTimeout(() => {
func.apply(this, args)
}, delay)
}
}
4. 쓰로틀
// 쓰로틀 : 일정 시간 동안 한 번만 실행. 함수(함수, 대시시간)
export const throttle = (func, limit = 300) => {
let inThrottle
return function (...args) {
// 일반 함수로 변경
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
setTimeout(() => (inThrottle = false), limit)
}
}
}
상품 ID를 통한 해당 상품 데이터 가져오기
- productApi.js
export const getProductById = async id => {
try {
const res = await axios.get(`${BASE_URL}/${id}`)
return res.data
} catch (err) {
console.log('[error]', err)
}
}
1️⃣ React Router Loader 방식
- loader에서 데이터 패칭
// ... 생략
import { getProductById } from './api/productApi'
const router = createBrowserRouter([
{
path: '/',
element: <Default />,
errorElement: <NotFound />,
children: [
{ path: '', element: <MainPage /> },
{ path: '/shop', element: <ShopPage /> },
{ path: '/about', element: <AboutPage /> },
{ path: '/blog', element: <BlogPage /> },
{ path: '/cart', element: <CartPage /> },
{
path: '/detail/:productId',
element: <DetailPage />,
loader: async ({ params }) => {
try {
const product = await getProductById(params.productId)
return product // 리턴 값을 컴포넌트(<DetailPage/>)에서 받을 수 있음
} catch (err) {
console.log('[error]', err)
}
},
},
],
},
])
export default router
- 컴포넌트에서 useLoaderData()로 받아오기
import React from 'react'
import { useLoaderData, useParams } from 'react-router-dom'
const DetailPage = () => {
const { productId } = useParams()
const product = useLoaderData()
console.log(productId, product)
return (
<main>
<h2>DetailPage</h2>
</main>
)
}
export default DetailPage
✅ 장점
- 라우터 단계에서 데이터 패칭 ➡️ 페이지 렌더 전 필요한 데이터가 준비됨
- SSR, preloading 등에서 유리함
- 동기화된 라우팅 & 데이터 처리 가능
2️⃣ 컴포넌트 내부에서 패칭
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
import { getProductById } from '../api/productApi'
const DetailPage = () => {
const { productId } = useParams()
const [product, setProduct] = useState({})
useEffect(() => {
const fetchProduct = async () => {
try {
const data = await getProductById(productId)
setProduct(data)
} catch (err) {
console.log('[error]', err)
}
}
fetchProduct()
}, [productId])
return (
<main>
<h2>DetailPage</h2>
<p>{product.title}</p>
</main>
)
}
export default DetailPage
Alias 경로 설정 추가
절대 경로 별칭(alias)을 설정해서 import 경로를 간편하게 줄이기 위한 설정
- 기존에 vite.config.js에 추가했지만, 컴포넌트 호출 시 제대로 경로 별칭 설정이 안됨
- jsconfig.json 파일에도 설정
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@hooks/*": ["src/hooks/*"],
"@pages/*": ["src/pages/*"],
"@utils/*": ["src/utils/*"],
"@assets/*": ["src/assets/*"],
"@api/*": ["src/api/*"],
"@store/*": ["src/store/*"]
}
}
}
🔸 vite.config.js
- Vite(번들러) 가 경로를 해석하는 용도
- 실제로 앱이 동작할 때, import 경로를 처리해줌
- 예: /src/components/... 이런 식의 import 처리
🔹 jsconfig.json / tsconfig.json
- 에디터(특히 VS Code) 를 위한 설정
- VS Code가 경로 자동완성, 모듈 추적, 오류 검사 등을 잘 하도록 도와줌
- 컴파일러(또는 타입스크립트)가 경로를 이해하게 해줌
개발 서버 proxy 설정 추가
- vite.config.js
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, ''),
},
},
},
server | Vite의 개발 서버 설정을 담는 최상위 객체 |
proxy | 실제 API 요청을 다른 서버로 중계해주는 프록시 설정. CORS 우회용 |
'/api' | 프론트에서 요청할 때 /api로 시작하는 경로를 감지해서 프록시를 적용함. 예: /api/products |
target | 실제로 요청을 보낼 백엔드 서버 주소. 여기선 http://localhost:3000 |
changeOrigin: true | 요청 헤더의 origin을 target의 origin으로 바꿔줌. 대부분의 CORS 문제 방지용 |
rewrite | 프록시로 넘기기 전에 경로를 바꿔줌. 여기선 /api 접두어를 제거함. /api/products ➡️ /products 로 바꿔서 보냄 |
- productApi.js
import axios from 'axios'
// const BASE_URL = 'http://localhost:3000/products'
export const getProductsData = async (query = '') => {
try {
const res = await axios.get(`/api/products/?${query}`)
return res.data
} catch (err) {
console.log('[error]', err)
// throw err // 호출된 곳으로 에러 던지기
}
}
export const getProductById = async id => {
try {
const res = await axios.get(`/api/products/${id}`)
return res.data
} catch (err) {
console.log('[error]', err)
}
}
🧩 왜 쓰는가?
- 프론트 서버와 백엔드 서버가 다른 포트에서 돌아가면 CORS 에러 발생 ➡️ 이를 우회하기 위해, Vite 개발 서버가 중간에서 프록시 역할을 해줌
- /api와 같은 접두어로 모든 API 요청을 통합하여 관리 ➡️ 실제 백엔드 URL이 변경되더라도 프론트엔드 코드를 수정할 필요가 없음
- 개발 환경과 프로덕션 환경에서 동일한 API 경로 사용 가능
HydrateFallback
- 초기 hydration 중에 보여줄 fallback UI를 의미
- 말 그대로 서버에서 렌더링된 콘텐츠가 클라이언트에서 React와 연결되기 전까지 임시로 보여줄 UI
예를 들어, 데이터를 서버에서 가져오고 그 데이터를 기반으로 SSR을 했는데, 클라이언트에서는 아직 그 데이터를 받아오지 못한 상태일 수도 있음. 이때 loading UI 등을 대신 보여주기 위해 HydrateFallback을 제공 가능
⚠️ No HydrateFallback element provided to render during initial hydration
👉🏻 데이터를 기다리는 동안 보여줄 임시 화면이 없다라는 의미
👉🏻 RouterProvider에게 "로딩 중..." 같은 간단한 대체 화면을 제공하면 됨
createRoot(document.getElementById('root')).render(
<StrictMode>
<RouterProvider router={router} fallbackElement={<div>로딩중...</div>} />
</StrictMode>
)
상품 상세 페이지 중간 점검 ~
728x90
반응형
'LG 유플러스 유레카 SW > React' 카테고리의 다른 글
[#58] React 쇼핑몰 - 장바구니 수정/삭제 + 반응형 Shop 페이지 (0) | 2025.04.21 |
---|---|
[#57] React 쇼핑몰 - 상품 상세 페이지(탭, 슬라이더) + 장바구니 조회/추가 (0) | 2025.04.18 |
[#55] React 쇼핑몰 - 스켈레톤 UI + 반응형 메인 리스트 페이지 (0) | 2025.04.16 |
[#54] React 쇼핑몰 - 성능 향상 + Hero 페이지 제작 (2) | 2025.04.15 |
[#53] React 쇼핑몰 - 헤더 반응형으로 만들어보기 (1) | 2025.04.14 |