페이지 주소를 정의할때 가끔은 유동적인 값을 사용해야 할때가 있다.
URL파라미터는 주소의 경로에 유동적인 값을 넣는 형태고, 쿼리스트링은 주소의 뒷부분에 ?
문자열 이후에 key=value로 값을 정의하며 &으로 구분하는 형태이다.
URL파라미터는 ID또는 이름을 사용하여 특정 데이터를 조회할때 사용한다
쿼리스트링은 키워드검색, 페이지네이션, 정렬 방식 등 데이터 조회에 필요한 옵션을 전달할 때 사용한다.
파라미터 사용법
src/pages/Profile.js
import {useParams} from 'react-router-dom'
const data = {
person:{
name:'jin',
description:'developer',
},
pet:{
name:'jolly',
description:'dog',
},
};
const Profile = ()=>{
// URL파라미터는 useParams라는 Hook을 사용하여 객체 형태로 조회가 가능하다.
// URL파라미터의 이름은 라우트 설정을 할 때 Route 컴포넌트의 path props를 통하여 설정한다.
const params = useParams();
const profile = data[params.username];
return(
<div>
<h1>프로필</h1>
{
profile ? (
<div>
<h2>{profile.name}</h2>
<p>{profile.description}</p>
</div>
) : (
<p>존재하지 않는 프로필입니다.</p>
)}
}
</div>
);
}
data객체에 예시 프로필 정보들은 key-value 형태로 담아졌다. 그리고, Profile
컴포넌트에서는 username
URL 파라미터를 통하여 프로필을 조회한 뒤에 프로필이 존재하지 않으면 존재하지 않는 프로필입니다.
라는 문구를 보여주고, 반대로 존재하면 프로필 정보를 보여준다.
src/App.js
import {Route, Routes} from 'react-router-dom';
import About from './pages/About';
import Home from './pages/Home';
import Profile from './pages/Profile';
const App = ()=>{
return (
<Routes>
<Route exact path="/" element={<Home/>}/>
<Route path="/about" element={<About/>}/>
<Route path="/profiles/:username" element={<Profile/>}/>
// URL파라미터는 /profiles/:username과 같이 경로에 :를 사용하여 설정한다.
// 만약 URL 파라미터가 여러개인 경우에 /profiles/:username/:field와 같은 형태로 설정 가능하다.
// URL파라미터로 설정된 :username은 Profile에 username이라는 params로 전달된다.
</Routes>
)
}
Profile
페이지로 이동을 할 수 있도록 Home
페이지에 Link
를 더 만든다. 링크가 여러개이기 때문에, ul
태그를 사용하여 리스트 형태로 보여준다.
src/pages/Home.js
import {Link} from 'react-router-dom'
const Home = () =>{
return(
<div>
<h1>홈</h1>
<ul>
<li>
<Link to="/about">소개</Link>
</li>
<li>
<Link to="/profiles/person">jin의 프로필</Link>
</li>
<li>
<Link to="/profiles/pet">jolly의 프로필</Link>
</li>
<li>
<Link to="/profiles/void">존재하지않는 프로필</Link>
</li>
</ul>
</div>
)
}
src/pages/Home.js
import React from "react";
const About = (props) => {
return <div>어바웃</div>;
};
export default About;
쿼리스트링을 사용할떄는 URL 파라미터와 다르게 Route
컴포넌트를 사용할 떄 별도로 설정할게 없다.
import {useLocation} from 'react-router-dom';
const About = ()=>{
const location = useLocation();
// location 객체를 반환하며 이객체는 현재 사용자가 보고있는 페이지의 정보를 지니고있다.
return(
<div>
<h1>소개</h1>
<p>리액트 라우터</p>
<p>쿼리스트링 : {location.search}</p>
</div>
)
}
export default About;
useLocation 은 다음 과 같은 값들을 가지고있다.
쿼리스트링 값을 key와 value로 파싱하는 작업은 npm에서 qs
또는 querystring
패키지를 설치해서 처리할수있다.
쿼리스트링을 따로 파싱까지 해야된다면 번거로울수도 있는데, 다행히 리액트 라우터 v6부터 useSearchParams
라는 Hook을 통해서 쿼리스트링을 더욱 쉽게 다룰수있다.
쿼리스트링 파싱
src/pages/About.js
import React from "react";
import { useSearchParams, useLocation } from "react-router-dom";
const About = (props) => {
const [searchParams, setSearchParams] = useSearchParams();
const detail = searchParams.get("detail");
const mode = searchParams.get("mode");
const location = useLocation();
console.log(location);
//"?mode=NaN&detail=true"
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>
<div>리액트</div>
<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 는 배열 타입의 값을 반환하며, 첫번째 원소는 쿼리파라미터를 조회하거나 수정하는 메소드들이 담긴 객체를 반환한다. get 메소드를 통해 특정 쿼리파라미터를 조회할 수있고, set메소드를 통해 특정 쿼리파라미터를 업데이트 할 수 있다. 만약 조회시에 쿼리파라미터가 존재하지않는다면 null로 조회된다. 두번째 원소는 쿼리파라미터를 객체형태로 업데이트 할수 있는 함수로 반환한다.
주의할점 : 쿼리파라미터를 조회할때 값은 무조건 문자열 타입이다.
-> true 또는 false값을 넣게 된다면 비교할때 꼭 'true'와 같이 따옴표를 감싸야한다. 숫자는 parseInt를 써서 변환해야한다.
src/pages/Articles.js
import React from "react";
import { Link } from "react-router-dom";
const Articles = (props) => {
return (
<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>
);
};
export default Articles;
src/pages/Articles.js
import React from "react";
import { useParams } from "react-router-dom";
const Article = (props) => {
const { id } = useParams();
return (
<div>
<h2>게시글 {id}</h2>
</div>
);
};
export default Article;
src/pages/Articles.js
import React from "react";
import { Route, Routes } from "react-router-dom";
import About from "./pages/About";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
const App = (props) => {
return (
<Routes>
<Route path="/" exact element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
<Route path="/articles" element={<Articles />} />
<Route path="/articles/:id" element={<Article />} />
</Routes>
);
};
export default App;
기존의 방식대로 하면 이런식으로 구조될수있지만, 중첩라우팅을 이용하게되면,
import React from "react";
import { Route, Routes } from "react-router-dom";
import About from "./pages/About";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
const App = (props) => {
return (
<Routes>
<Route path="/" exact element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
<Route path="/articles" element={<Articles />}>
// <Route path="/articles/*" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
</Routes>
);
};
export default App;
Outlet이라는 컴포넌트를 사용하게되면, Route
children으로 들어가는 JSX엘리먼트를 보여주는 역할은 한다.
Outlet은 부모 Route에 꼭 넣어야 자식들을 렌더링할수있다.
exact 안 쓰먄 /*
가 필수입니다.
src/pages/Home.js
import React from "react";
import { Link } from "react-router-dom";
const Home = (props) => {
return (
<div>
<h1>홈화면</h1>
<ul>
<li>
<Link to="/about">소개</Link>
</li>
<li>
<Link to="/profiles/person">jin의 소개</Link>
</li>
<li>
<Link to="/profiles/pet">dog의 소개</Link>
</li>
<li>
<Link to="/profiles/void">존재하지 않는 프로필</Link>
</li>
<li>
<Link to="/articles">게시글 목록</Link>
</li>
</ul>
</div>
);
};
export default Home;
중첩된 라우트와 Outlet
은 페이지끼리 공통적으로 보여줘야 하는 레이아웃이 있을때도 사용가능하다.
예로, Home
, About
, Profile
페이지에서 상단에 헤더를 보여줘야 하는 상황을 가정해보자. 첫번째로 드는 생각은 아마 Header
컴포넌트를 따로 만들어주고 각 페이지 컴포넌트에서 재사용을 하는 방법이다. 첫번째 방법도 좋은 방법이지만 Outlet을 사용하면 보다 쉽게 구현가능하다. 중첩된 라우트를 사용하는 방식을 사용하면 컴포넌트를 한번만 사용해도 된다는 장점이있다.
src/Layout.js
import React from "react";
import { Outlet } from "react-router-dom";
const Layout = (props) => {
return (
<div>
<header style={{ background: "black", color: "#fff", padding: "20px" }}>
Header
</header>
<main>
<Outlet />
</main>
</div>
);
};
export default Layout;
####2-3 index props
Route
컴포넌트에는 index라는 props가있다. 이 props는 path="/"
와 동일하다.
...
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Route>
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
</Routes>
...
/경로로 들어 갔을때, 여전히 Home
페이지가 여전히 잘나온다. index prop은 상위 라우트의 경로와 일치하지만, 그 이후에 경로가 주어지지 않았을때 보여지는 라우트를 설정할때 사용한다. path="/"
와 동일한 역할을 하며 이를 좀더 명시적으로 표현하는 방법이다.
리액트 라우터에서 제공하는 유용한 API들
useNavigate
는 Link
컴포넌트를 사용하지 않고 다른 페이지로 이동을 해야하는 상황에 사용하는 Hook이다.
src/Layout.js
import React from "react";
import { Outlet, useNavigate } from "react-router-dom";
const Layout = (props) => {
const navigate = useNavigate();
const goBack = () => {
// 이전 페이지로 이동
navigate(-1);
};
const goArticles = () => {
// articles 경로로 이동
navigate("/articles");
};
return (
<div>
<header style={{ background: "black", color: "#fff", padding: "20px" }}>
<button onClick={goBack}>뒤로가기</button>
<button onClick={goArticles}>게시글 목록</button>
</header>
<main>
<Outlet />
</main>
</div>
);
};
export default Layout;
navigate
함수를 사용할 때 파라미터 숫자 타입이라면 앞으로가거나, 뒤로 간다. navigate(-1)은 한번 뒤로가고, navigate(-2)은 뒤로 두번간다. 반대로 navigate(1)은 앞으로 한번간다. 하지만 이조건은 뒤로가기를 한번 한 상태일때 가능하다.
replace는 페이지를 이동할때 현재 페이지를 페이지 기록에 남기지않는다.
const goArticles = () => {
navigate('./articles', {replace:true})
};
/ 경로로 돌아간뒤, Home페이지에서 About페이지로 이동후 상단의 게시글 목록페이지로 이동을 해본다. 그리고 그상태에서 브라우저의 뒤로가기 버튼을 눌러 이전페이지로 이동시에 replace설정 때문에 About페이지가 아닌 Home페이지가 나타나게된다.
NavLink
컴포넌트는 링크에서 사용하는 경로가 현재 라우트의 경로와 일치하는 경우 특정 스타일 또는 CSS클래스를 적용하는 컴포넌트이다.
이 컴포넌트를 사용할때는 style또는 className을 설정할때 {isActive:boolean}을 파라미터로 전달받는 함수 타입의 값을 전달하게된다.
<NavLink
style={({isActive}) => isActive ? activeStyle : undefined}
/>
or
<NavLink
className={({isActive}) => isActive ? 'active' : undefined}
/>
src/pages/Articles.js
import React from "react";
import { NavLink, Outlet } from "react-router-dom";
const Articles = (props) => {
// console.log(<Outlet />);
return (
<div>
<Outlet />
<ul>
<li>
<NavLink
to="/articles/1"
style={({ isActive }) => (isActive ? { color: "red" } : undefined)}
>
게시글 1
</NavLink>
</li>
<li>
<NavLink
to="/articles/2"
style={({ isActive }) => (isActive ? { color: "red" } : undefined)}
>
게시글 2
</NavLink>
</li>
<li>
<NavLink
to="/articles/3"
style={({ isActive }) => (isActive ? { color: "red" } : undefined)}
>
게시글 3
</NavLink>
</li>
</ul>
</div>
);
};
export default Articles;
위와같이 활성화된 메뉴에 스타일을 줄수있따.
하지만 위와 같은 코드는 반복되는 코드가 여러번 사용되고있따. 이럴때는 NavLink
를 감싼 또 다른 컴포넌트를 만들어서 다음과 같이 리팩토링하여 사용할수있다.
src/pages/Articles.js
import React from "react";
import { NavLink, Outlet } from "react-router-dom";
const Articles = (props) => {
// console.log(<Outlet />);
return (
<div>
<Outlet />
<ul>
<AricleItem id={1} />
<AricleItem id={2} />
<AricleItem 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;
사전에 정의되지 않는 경로에 사용자가 진입했을때 보여주는 페이지이다. 즉 페이지를 찹을 수 없을때 나타는 페이지이다.
src/pages/NotFound.js
import React from "react";
const NotFound = (props) => {
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 64,
position: "absolute",
width: "100%",
height: "100%",
}}
>
404
</div>
);
};
export default NotFound;
src/App.js
import React from "react";
import { Route, Routes } from "react-router-dom";
import About from "./pages/About";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Layout from "./Layout";
import NotFound from "./pages/Profile";
const App = (props) => {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Route>
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
<Route path="*" element={<NotFound />} />
// wildCard문자
</Routes>
);
};
export default App;
*
는 wildCard문자로, 아무텍스트나 매칭한다는 뜻이다. 라우트 엘리먼트 상단의 위치하는 라우트들의 규칙을 모두 확인후, 일치하지않으면 이화면이 나타난다.
Navigate 컴포넌트는 화면에 보여주는 순간 다른 페이지로 이동을 하고 싶을때 사용하는 컴포넌트이다. 페이지를 리다이렉트 하고싶을때 사용한다.
예로 로그인이 필요한 상황에서 로그인이 되지않았으면 로그인페이지로 리다이렉트한다.
src/pages/Login.js
import React from "react";
const Login = (props) => {
return <div>로그인 페이지</div>;
};
export default Login;
src/pages/MyPage.js
import React from "react";
import { Navigate } from "react-router-dom";
const MyPage = (props) => {
const isLoggedIn = false;
if (!isLoggedIn) {
return <Naviagte to="/login" replace={true} />;
}
return <div>마이페이지</div>;
};
export default MyPage;
src/App.js
import React from "react";
import { Route, Routes } from "react-router-dom";
import About from "./pages/About";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Layout from "./Layout";
import NotFound from "./pages/Profile";
import Login from "./pages/Login";
import MyPage from "./pages/MyPage";
const App = (props) => {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Route>
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="/mypage" element={<MyPage />} />
<Route path="*" element={<NotFound />} />
</Routes>
);
};
export default App;