사용자가 요청한 URL에 따라 알맞은 페이지를 보여주는 것을 말한다
여러 페이지로 구성된 웹 애플리케이션을 만들 때 페이지 별로 컴포넌트들을 분리해가며 프로젝트를 관리하기 위해 필요한 것이 라우팅 시스템이다
리액트 라우터
리액트의 라우팅 관련 라이브러리들 중 가장 오래됐고, 가장 많이 사용되는 라이브러리
컴포넌트 기반으로 라우팅 시스템을 설정할 수 있다
Next.js
리액트 프로젝트의 프레임워크
리액트 프로젝트 설정을 하는 기능, 라우팅 시스템, 최적화, 다국어 시스템 지원, 서버 사이드 렌더링 등 다양한 기능을 제공한다
라우팅 시스템은 파일 경로 기반으로 작동하며 리액트 라우터의 대안으로 많이 사용되고 있다
SPA란 하나의 페이지로 이루어진 애플리케이션을 뜻한다
멀티 페이지 애플리케이션에서는 사용자가 다른 페이지로 이동할 때마다 새로운 html을 받아오고, 페이지를 로딩할 때마다 서버에서 CSS, JS, 이미지 파일 등의 리소스를 전달받아 브라우저 화면에 보여주었다
즉, 각 페이지마다 다른 html 파일이 존재했다
사용자 인터랙션이 많고 다양한 정보를 제공하는 모던 웹 애플리케이션은 이 방법이 적합하지 않다
👉 새로운 페이지를 보여줄 때마다 서버 측에서 제공하기 때문에 그만큼 서버의 자원을 사용하는 것이고, 트래픽도 더 많이 나오기 때문
그래서 리액트 같은 라이브러리를 사용해 뷰 렌더링을 사용자의 브라우저가 담당하도록 하고, 웹 애플리케이션을 브라우저에 불러와 실행시킨 후 사용자와의 인터랙션이 발생하면 필요한 부분만 JS를 사용해 업데이트하는 방식을 사용하게 됐다
새로운 데이터가 필요하면 서버 API를 호출해 필요한 데이터만 불러오면 된다
싱글 페이지 애플리케이션 : html은 한 번만 받아와서 웹 애플리케이션을 실행시킨 후, 이후에는 필요한 데이터만 받아와 화면에 업데이트하는 것
리액트 라우터 같은 라우팅 시스템은 사용자의 브라우저 주소창 경로에 따라 알맞은 페이지를 보여준다
이후 링크를 눌러 다른 페이지로 이동할 때 브라우저의 History API
를 사용해 브라우저의 주소창 값만 변경하고 기존에 페이지에 띄웠던 웹 애플리케이션을 그대로 유지하며 라우팅 설정에 따라 또 다른 페이지를 보여준다
src/index.js
파일에서 BrowserRouter
라는 컴포넌트를 사용해 감싸면 된다
BrowserRouter : 웹 애플리케이션에 History API를 사용해 페이지를 새로 불러오지 않고도 주소를 변경하고 현재 주소 경로에 관련된 정보를 리액트 컴포넌트에서 사용할 수 있도록 해 준다
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
사용자의 브라우저 주소 경로에 따라 원하는 컴포넌트를 보여주기 위해 Route
라는 컴포넌트를 통해 라우트 설정을 해줘야 한다
<Route path="주소규칙" element={보여 줄 컴포넌트 JSX} />
Route 컴포넌트는 Routes 컴포넌트 내부에서 사용되어야 한다
/profile/velopert
/articles?page-1&keyword=react
URL 파라미터는 주소의 경로에 유동적인 값을 넣는 형태, 쿼리스트링은 주소의 뒷부분에 ? 문자열 이후 key=value
로 값을 정의하며 &로 구분하는 형태다
URL 파라미터는 주로 ID 혹은 이름을 사용해 특정 데이터를 조회할 때 사용하고, 쿼리스트링은 키워드 검색, 페이지네이션, 정렬 방식 등 데이터 조회에 필요한 옵션을 전달할 때 사용한다
const Profile = () => {
const params = useParams();
const profile = data[params.username]; // URL 파라미터의 이름이 username이라서 이렇게 표현
return (
<div>
<h1>사용자 프로필</h1>
{profile ? (
<div>
<h2>{profile.name}</h2>
<p>{profile.description}</p>
</div>
) : (
<p>존재하지 않는 프로필</p>
)}
</div>
);
};
URL 파라미터는 useParams라는 Hook을 사용해 객체 형태로 조회할 수 있다
<Route path="/profiles/:username" element={<Profile />} />
:username처럼 path props를 통해 URL 파라미터의 이름을 설정할 수 있다
username이 아닌 다른 이름으로 해도 무관, : 뒤에 적어야 함
URL 파라미터가 여러 개인 경우에는 /profiles/:username/:field
와 같은 형태로 설정할 수 있다
쿼리스트링은 URL 파라미터와 달리 Route 컴포넌트를 사용할 때 별도로 설정해야 하는 것이 없다
이 Hook은 location 객체를 반환하며 다음과 같은 값들을 가진다
주소창에 your주소?detail=true&mode=1
이라고 입력 후 location.search
를 해보면 ?detail=true&mode=1
을 나타낸다
?를 지우고 &로 분리해 key와 value를 파싱하는 작업은 리액트 라우터 v6부터
useSearchParams
Hook을 통해 처리할 수 있다
const About = () => {
const [searchParams, setSearchParams] = useSearchParams();
const detail = searchParams.get("detail");
const mode = searchParams.get("mode");
const onToggleDetail = () => {
setSearchParams({ mode, detail: detail === "true" ? false : true });
};
const onIncreaseMode = () => {
const nextMode = mode === null ? 1 : parseInt(mode) + 1;
setSearchParams({ mode: nextMode, detail });
};
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해 보는 프로젝트</p>
<p>detail: {detail}</p>
<p>mode: {mode}</p>
<button onClick={onToggleDetail}>Toggle detail</button>
<button onClick={onIncreaseMode}>mode+1</button>
</div>
);
};
export default About;
useSearchParams는 배열 타입의 값을 반환하며, 첫 번째 원소는 쿼리파라미터를 조회하거나 수정하는 메서드들이 담긴 객체를 반환한다 (useState와 비슷)
get 메서드로 특정 쿼리파라미터를 조회할 수 있고, set 메서드로 업데이트할 수 있다
조회 시 쿼리파라미터가 존재하지 않는다면 null로 조회된다
두 번쨰 원소는 쿼리파라미터를 객체 형태로 업데이트할 수 있는 함수를 반환한다
📢 쿼리파라미터 사용할 때 주의점
쿼리파라미터를 조회할 때 값은 무조건 문자열 타입ex) true나 false 값을 넣는다면 값을 비교할 때 꼭 'true' 처럼 따옴표로 감싸야 하고 숫자를 다룰 때는 parseInt를 사용해 숫자 타입으로 변환해야 한다
<Route path="/articles" element={<Articles />} />
<Route path=":id" element={<Article />} />
보통은 이렇게 라우트 설정을 하는데, 이렇게 하면 게시글 목록 페이지에서 게시글을 열었을 때 하단에 목록을 보여주고 싶은 경우
<div>
<h2>게시글 {id}</h2>
<ArticleList />
</div>
이렇게 컴포넌트를 따로 만들어 사용해야한다
중첩된 라우트를 사용하면 Outlet 컴포넌트를 사용해 좀 더 간결하게 구현할 수 있다
<Route path="/articles" element={<Articles />}> <Route path=":id" element={<Article />} /> </Route>
Outlet은 Route의 children으로 들어가는 JSX 엘리먼트를 보여주는 역할을 한다
const Articles = () => {
return (
<div>
<Outlet />
<ul>
<li>
<Link to="/articles/1">게시글 1</Link>
</li>
<li>
<Link to="/articles/2">게시글 2</Link>
</li>
<li>
<Link to="/articles/3">게시글 3</Link>
</li>
</ul>
</div>
);
};
export default Articles;
Outlet 컴포넌트가 사용된 자리에 중첩된 라우트(여기서는 Article 컴포넌트)가 보여지게 된다
중첩된 라우트와 Outlet은 공통적으로 보여줘야 하는 레이아웃 (header, nav, sidebar 등)이 있을 때도 유용하다
Header 컴포넌트를 따로 만들어두고 각 페이지 컴포넌트에서 재사용해도 되지만 중첩된 라우트를 사용하면 컴포넌트를 한번만 사용해도 된다는 장점이 있다
Route 컴포넌트에는 index props가 존재하는데, 이 props는 path="/"
와 동일한 의미를 가진다
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Route>
위 예제처럼 index를 사용하면 상위 경로에 /about
과 같은 추가 경로가 붙지 않은 기본 상태에서 보여줄 라우트를 설정할 수 있다
Link 컴포넌트를 사용하지 않고 다른 페이지로 이동해야 하는 상황에 사용하는 Hook
navigate 함수의 파라미터가 숫자 타입이면 앞으로 가거나, 뒤로 간다
ex) navigate(-1) : 뒤로 한 번, navigate(-2) : 뒤로 두 번, navigate(1) : 앞으로 한 번
다른 페이지로 이동할 때 replace
옵션을 사용하면 현재 페이지를 기록에 남기지 않는다
const goArticles = () => {
navigate('/articles', { replace: true });
}
링크에서 사용하는 경로가 현재 라우트의 경로와 일치하는 경우 특정 스타일 또는 CSS 클래스를 적용하는 컴포넌트
const Articles = () => {
return (
<div>
<Outlet />
<ul>
<ArticleItem id={1} />
<ArticleItem id={2} />
<ArticleItem id={3} />
</ul>
</div>
);
};
const ArticleItem = ({ id }) => {
const activeStyle = {
color: "green",
fontSize: 21,
};
return (
<li>
<NavLink
to={`/articles/${id}`}
style={({ isActive }) => (isActive ? activeStyle : undefined)}
>
게시글 {id}
</NavLink>
</li>
);
};
export default Articles;
사전에 정의되지 않는 경로에 사용자가 진입했을 때 보여주는 페이지
<Route path="*" element={<NotFound />} />
*는 wildcart 문자로 아무 텍스트나 매칭한다는 뜻이다
해당 라우트 상단에 위치하는 라우트 규칙을 모두 확인 후 일치하는 라우트가 없다면 실행된다
컴포넌트를 화면에 보여주는 순간 리다이렉트하고 싶을 때 사용하는 컴포넌트
const Mypage = () => {
const isLoggedIn = false;
if (!isLoggedIn) return <Navigate to="/login" replace={true} />;
return <div>마이 페이지</div>;
};
export default Mypage;
브라우저 주소창에 /mypage
경로를 직접 입력해서 들어가면 페이지가 로딩되는 순간 Login 페
이지로 이동된다