기존에 띄웠던 웹 애플리케이션을 그대로 유지하면서 라우팅 설정에 따라 또 다른 페이지를 보여주는 SPA를 react router dom을 사용하여 구현해봤다.
1. 기본 사용법
index.js에 react router dom에 내장되어있는 BrowserRouter로 감싸야한다.
이 컴포넌트는 브라우저의 history API를 사용해 html을 새로 불러오지 않고도 주소를 변경하고, 현재 주소의 경로에 저장된 정보를 컴포넌트에서 사용할 수 있게 해준주는 역할을 한다.
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
2. URL 파라미터와 쿼리스트링
- URL 파라미터: /profile/nayoung3669 (주소 경로에 유동적인 값을 넣음)
- 쿼리스트링 예시: /articles?page=1&keyword=react (?문자열 이후 key=value로 값을 정의하며, &로 구분하는 형태)
URL 파라미터는 주로 id 또는 이름으로 특정 데이터를 조회하고, 쿼리 스트링은 키워드 검색, 페이지네이션, 정렬 방식 등 데이터 조회에 필요한 옵션을 전달할 때 쓰인다.
URL 파라미터 전달하기 = useParams()를 사용한다.
//App.js
<Route path = "/profiles/:username" element={<Profile />}/>
Params 전달 (Home.js)
//Home.js
const Home = () => {
return(
<div>
<h1>HOME</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
<Link to="/about">소개</Link>
<Link to="/profiles/nayoung">nayoung의 프로필</Link>
<Link to="/profiles/gildong">gildong 프로필</Link>
</div>
)
}
import { useParams } from "react-router-dom"
//mock data
const data = {
nayoung : {
name: "김나영",
description: "리액트를 좋아하는 개발자",
},
gildong : {
name: "홍길동",
description: "자바를 좋아하는 개발자"
}
}
// params.username 으로 보내준 profile이 존재하면 보여주기, 없다면 없다고 보여주기
const Profile = () => {
const params = useParams();
const profile = data[params.username] //nayoung key를 가진 value 객체가 담김
return (
<div>
<h1> 사용자 프로필 </h1>
{profile ? (
<div>
{profile.name}
{profile.description}
</div>
):(
<div>
<p>존재하지 않는 프로필입니다.</p>
</div>
)}
</div>
)
}
export default Profile
만약 경로에 params 를 더 설정하고 싶다면
/profiles/:username/:field 와 같이 설정할 수 있다.
쿼리스트링
uselocation으로 자신이 보고있는 화면의 정보를 확인해보자
const About = () => {
const location = useLocation();
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해보는 페이지입니다.</p>
<p>쿼리 스트링: {location.search}</p>
{/* 현재 보고있는 페이지의 정보 */}
</div>
)
}
아래와 같이 출력된다. 이는 쿼리스트링 값을 임의로 주소입력창에 입력한 결과다.
qs라는 라이브러리를 통해 이 key와 value 들을 파싱할 수 있지만, 리액트 라우터에서는 useSearchParams 라는 Hooks를 제공한다. 이를 이용해 쉽게 쿼리 스트링을 파싱할 수 있다.
-useSearchParams로 쿼리 스트링 파싱하기!
const [searchParams, setSearchParams] = useSearchParams();
//searchParams : 조회, 수정 가능한 메소드를 모은 객체가 담겨있음
//get으로 조회, set으로 업데이트
const detail = searchParams.get('detail')
const mode = searchParams.get('mode')
쿼리 보여주기
About.js
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해보는 페이지입니다.</p>
<p>쿼리 스트링: {location.search}</p>
<p>detail: {detail}</p>
<p>mode: {mode}</p>
<button onClick={onToggleDetail}>Toggle detail</button>
<button onClick={onIncreaseMode}>Mode + 1</button>
</div>
)
toggle detail, increase mode 의 onClick 핸들러
const onToggleDetail = () => {
setSearchParams({mode, detail: detail === "true" ? "false" : "true"}) // 쿼리는 문자열이므로 "boolean"
}
const onIncreaseMode = () => {
const nextMode = mode === null ? 1 : parseInt(mode) + 1
setSearchParams({mode : nextMode ,detail})
}
쿼리파라미터는 문자열 타입이니 연산을 할 때는 parseInt로 숫자형으로 바꿔줘야한다.
boolean toggle 도 문자열로 평가해야한다.
3. 중첩된 라우트
게시글 목록인 Articles 를 보여주고, 그 안에 개별 게시글인 Article을 중첩시키면서 :id 파라미터를 전달해준다.
function App() {
return (
<div className="App">
<Routes>
<Route path = "/" element={<Home />}/>
<Route path = "/about" element={<About />}/>
<Route path = "/profiles/:username" element={<Profile />}/>
<Route path = "/articles" element={<Articles />}>
<Route path = ":id" element = {<Article />}/>
</Route>
</Routes>
</div>
);
}
이때는 리액트 라우터에서 제공하는 Outlet 이라는 컴포넌트를 Articles 컴포넌트에 사용해줘야한다. 이 컴포넌트는 중첩된 children 으로 들어가는 JSX를 보여주는 역할을 한다.
즉,
<Route path=":id" element ={<Article />} />
이 부분이 보여지게 된다.
그래서 게시글 목록을 같이 보여주고 싶으면 Outlet을 사용하면 된다.
import { Link, Outlet } from "react-router-dom"
const Articles = () => {
return (
<div>
{/* Article */}
<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
그리고 articles/1 에 들어가 게시물을 확인해보면
게시물이 Outlet 자리에 보여지고 , 그 아래 Articles 라는 게시물 목록이 보여진다.
이 Outlet 컴포넌트는 한 페이지에서 공통적으로 보여줘야 하는 레이아웃이 있을 때에도 유용하게 쓰인다. 예를 들면 Header , nav bar 같은 경우 이 중첩된 라우트와 Outlet을 활용해서 구현할 수도 있다.
//App.js
function App() {
return (
<div className="App">
<Routes>
<Route element={<Layout />}>
{/* header가 포함되어 있음 */}
<Route index element={<Home />}/>
<Route path = "/about" element={<About />}/>
<Route path = "/profiles/:username" element={<Profile />}/>
<Route path = "/articles" element={<Articles />}>
<Route path = ":id" element = {<Article />}/>
</Route>
</Route>
</Routes>
</div>
);
}
index는 path="/"와 같다. 조금 더 명시적으로 표현해준다.
Layout.js
import { Outlet } from "react-router-dom"
const Layout = () => {
return (
<div>
<header style={{ background: 'lightgray', padding: 20 , fontSize: 24 }}>
Header
</header>
<main>
<Outlet />
</main>
</div>
)
}
export default Layout
그러면 어느 페이지에 가도 Header가 보이게 된다.
4. 리액트 라우터 부가 기능
1) useNavigate : Link 컴포넌트를 이용하지 않고 페이지를 이동하는 경우 사용하는 hook
const Layout = () => {
const navigate = useNavigate();
const goBack = () => {
navigate(-1) //뒤로 이동 (1은 앞으로, -2는 두번 뒤로)
}
const goArticles = () => {
navigate('/articles') // 게시글 목록으로 이동
}
//return (...)
}
2번 뒤로의 -1는 replac : true 속성을 추가해준 것과 같다.
2)NavLink : 링크에서 사용하는 경로가 현재 라우트의 경로와 일치한다면 특정 스타일 또는 css 클래스를 적용하는 컴포넌트
const Articles = () => {
const activeStyle = {
color: "green",
fontSize: 21,
}
return (
<div>
{/* Article */}
<Outlet />
{/* Artivles */}
<ul>
<li>
<NavLink to="/articles/1" style={({isActive}) => isActive ? activeStyle : undefined} >게시글 1</NavLink>
</li>
<li>
<NavLink to="/articles/2" style={({isActive}) => isActive ? activeStyle : undefined} >게시글 2</NavLink>
</li>
</ul>
</div>
)
}
export default Articles
isActive : boolean 을 제공하기 때문에 현재 Active 상태라면 특정 css style 을 추가해줄 수 있다. 개인적으로 굉장히 신기했음..!!!
3) NotFound 페이지 만들기
//App.js
<Route path="*" element={<NotFound />}/>
이후 NotFound.js 에서 컴포넌트를 만들면 된다. 일치하는 라우트가 없다면 이 라우트가 화면에 뜨게 된다.
4) Navigate 컴포넌트 (ex. 로그인이 필요합니다!)
const Mypage = () => {
const isLoggedIn = false;
if (!isLoggedIn) {
return <Navigate to="/login" replace={true}/>
}
return (
<div>
마이페이지
</div>
)
}
Login을 하지 않고 Mypage 에 접속하면 isLoggedIn 이 false이므로 Login으로 navigate 된다.
Reference
[리액트를 다루는 기술] 김민준
'Computer Programming > [리액트를 다루는 기술]' 카테고리의 다른 글
[리액트를 다루는 기술] 15. Context API 연습 w/ Vite (0) | 2023.06.24 |
---|---|
[리액트를 다루는 기술] 14. API 연동 뉴스 뷰어 (NavLink, useParams, usePromise Custom Hook) (0) | 2023.06.24 |
React immer 라이브러리로 불변성 유지하기 (0) | 2023.06.20 |
react-virtualized를 사용한 렌더링 최적화 (0) | 2023.06.20 |
[리액트를 다루는 기술] 11. 컴포넌트 성능 최적화 (1) | 2023.06.19 |