입력할 때마다 서버에 검색 요청을 보낸다고 하면, 총 3번의 요청이 생깁니다
fetch('.../search?q=고')
fetch('.../search?q=고양')
fetch('.../search?q=고양이')
여기서 문제는 첫 번째 요청이 느리게 응답하고 세 번째 요청이 먼저 도착하는 경우가 있을 수 있다. 그러면 결과가 뒤섞일 수 있습니다. 그렇게 되면 아래와 같은 문제가 생김
1.응답 순서가 요청 순서랑 다르면 결과가 뒤바뀜
2.불필요한 요청이 많아지면 트래픽 낭비, 서버 부하
3.검색 결과가 깜빡이거나 느려짐
그러한 문제점을 해결할 수 있는 것이 => AbortController + cleanup function
fetch() 요청을 취소할 수 있는 컨트롤러.
컴포넌트가 언마운트되거나 의존성 배열 값이 바뀔 때 자동으로 실행됨.
import { useEffect, useState } from 'react';
import axios from 'axios';
function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
if (!query) return;
const controller = new AbortController(); // 1. AbortController 생성
const fetchData = async () => {
try {
const res = await axios.get('https://api.example.com/search', {
params: { q: query },
signal: controller.signal, // 2. axios 요청에 연결
});
setResults(res.data.results);
} catch (error) {
if (axios.isCancel(error)) {
console.log('axios 요청 취소됨');
} else if (error.name === 'CanceledError') {
console.log('요청이 중단됨 (AbortController)');
} else {
console.error('요청 실패:', error);
}
}
};
fetchData();
// 3. cleanup: query가 바뀌면 이전 요청 취소
return () => {
controller.abort();
};
}, [query]);
return (
<div>
<input
type="text"
placeholder="검색어 입력"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<ul>
{results.map((item, idx) => (
<li key={idx}>{item.name}</li>
))}
</ul>
</div>
);
}
React Router는 리액트에서 라우팅을 구현하기 위한 라이브러리이다. 여러 페이지를 쉽게 관리하고 내비게이션을 구현할 수 있게 해줌. 여기서 라우팅은 URL에 따라 다른 콘텐츠를 사용자에게 보여주는 것을 말한다.
현재 컴포넌트의 props와 state의 상태에 기초하여 UI를 어떻게 구성할지 컴포넌트에게 요청하는 작업을 의미함.
리액트는 대표적인 CSR 라이브러리입니다.
CSR은 말 그대로 브라우저(클라이언트)에서 화면을 렌더링하는 방식을 의미합니다.
CSR 방식에서는 사용자가 웹사이트에 접속할 때, 서버로부터 완성된 HTML을 받는 것이 아니라, 최소한의 HTML과 JavaScript 파일만 내려받습니다. 이후 브라우저에서 JavaScript가 실행되면서 화면을 동적으로 구성하게 됩니다.
이 방식은 초기 화면 로딩 속도가 느릴 수 있다는 단점이 있지만, 한 번 로딩된 이후에는 페이지 간 이동이 매우 빠르며, 새로고침 없이 필요한 데이터만 가져와 화면을 구성하기 때문에 사용자 경험(UX) 측면에서 매우 뛰어납니다.
빠른 페이지 전환
한번 로딩되면 이후 페이지 전환은 서버 요청 없이 클라이언트 내에서 이루어지기 때문에 부드럽고 빠릅니다.
상태 유지가 쉬움
클라이언트에서 상태를 관리하기 때문에 페이지를 이동해도 상태가 유지됩니다.
초기 로딩 속도 저하
JavaScript가 실행되기 전까지 사용자에게 아무것도 보여주지 못해 초기 체감 속도가 느릴 수 있습니다.
SEO 최적화 어려움
HTML 콘텐츠가 없는 상태로 전달되기 때문에 검색 엔진이 콘텐츠를 인식하기 어려워 검색 노출에 불리할 수 있습니다.
npm i react-router-dom
기존에 BrowserRouter 방식 (Declarative 방식)만을 사용했는데 이번에 강사님께서 다른 방법을 알려주셨다 좀 더 실무에 많이 쓰이고 효율적인 방법이라고 알려주셨다.
이 방식은 중첩 라우트 구조를 객체 형태로 명확히 표현할 수 있어 유지보수가 편리하며, layout 컴포넌트와 함께 사용할 때 직관적인 설계가 가능합니다.
import React from 'react';
import { Outlet } from 'react-router-dom';
const Default = () => {
return (
<div>
<Header />
{/* Outlet은 라우터에 의해서 보여질 컴포넌트 */}
<Outlet />
<Footer />
</div>
);
};
export default Default;
은 중첩 라우트에서 자식 컴포넌트를 보여주는 자리
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Default from './layout/default';
import MainPage from './pages/MainPage';
import AboutPage from './pages/AboutPage';
import ShopPage from './pages/ShopPage';
import BlogPage from './pages/BlogPage';
import MainSub1Page from './pages/MainSub1Page';
import MainSub2Page from './pages/MainSub2Page';
const router = createBrowserRouter([
{
element: <Default />,
children: [
{
path: '/',
element: <MainPage />,
children: [
{ path: '/sub1', element: <MainSub1Page /> },
{ path: '/sub2', element: <MainSub2Page /> },
],
},
{ path: '/about', element: <AboutPage /> },
{ path: '/shop', element: <ShopPage /> },
{ path: '/blog', element: <BlogPage /> },
],
},
{ path: '*', element: <MainPage /> },
]);
export default function Router() {
return <RouterProvider router={router} />;
}
NavLink 컴포넌트는 현재 경로와 일치할 때 자동으로 active 클래스를 추가합니다.
이걸 사용해 현재 위치한 페이지 nav에 효과를 줄 때 사용 가능하다.
개발자 도구를 통해 swiper에 기본으로 설정된 클래스네임을 사용해 변경이 가능하다.
/* Swiper */
.swiper {
width: 100%;
}
.swiper-slide {
text-align: center;
background: gainsboro;
/* Center slide text vertically */
display: flex;
justify-content: center;
align-items: center;
}
.swiper-slide img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.swiper-pagination {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0.8rem;
}
.swiper-pagination-bullet {
opacity: 1;
background-color: white;
}
.swiper-pagination-bullet-active {
background-color: transparent;
width: 1.6rem;
height: 1.6rem;
border: 1px solid white;
}