react-router-dom

pyozz·2024년 1월 20일
post-thumbnail

react-router-dom이란?

SPA를 구현하기 위해 리액트에서 페이지 이동을할 때 사용하는 라이브러리

yarn add react-router-dom 명령어로 설치를 하고 가장 먼저 할 일은 main.jsx 파일에서 App 컴포넌트를 BrowserRouter 컴포넌트로 감싸준다.

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { BrowserRouter } from 'react-router-dom'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
  	<BrowserRouter>
  		<App />
  	</BrowserRouter>
  </React.StrictMode>,

이렇게 감싸주면 감싸는 내부에서 react-router-dom이 제공하는 router 기능을 사용할 수 있게된다.

이후 두 개의 컴포넌트를 생성하고 주소를 통해 각 컴포넌트에 접근할 수 있도록 연결시키기 위해 App 컴포넌트에 Routes 태그를 작성한다. 이 태그는 각각의 주소를 명시하기 위한 컴포넌트의 부모 태그 역할을 한다.

import React from 'react'
import { Routes, Route } from 'react-router-dom'
import Posts from './components/Posts/Posts'
import Users from './components/Users/Users'

function App() {
  return (
    <div>
      <Routes>
      	<Route path='posts' element={<Posts />}/>
		<Route path='users' element={<Users />}/>
  		...
      </Routes>
    </div>
  )
}

export default App

이 두 개 컴포넌트 간의 이동에는 a태그가 아닌 Link 컴포넌트를 사용한다. a태그를 사용하면 브라우저에서는 페이지를 새로 불러오기 때문이다.
Link 컴포넌트도 a태그를 사용하긴 하지만 페이지를 새로 불러오는 것을 막고 내부적으로 history API를 사용해서 주소의 경로만 바꾸는 원리이다.

<nav>
	<Link to="/posts">Posts</Link>
	<Link to="/users">Users</Link>
</nav>

만약 사용자가 존재하지 않는 페이지로 접근하게 되면 이에 대해 알려주는게 좋은데 이때 마지막 Route의 path에 *를 입력한다.

// 위에 명시한 Route들에 일치하는 경로가 없다면 * path를 갖는 컴포넌트를 보여준다.
...
<Route path="*" element={컴포넌트} />

Nested Route와 Outlet 태그

서브 라우트에서 공통적으로 표시할 내용이 있는 경우 사용한다.

<Route path="posts" element={<Posts/>}>
	{/* 상위 path를 이어받는다. ex) 주소/posts/2 */}
	<Route path=":postId" element={<PostDetail/>}/>
</Route>

이러한 형태를 nested route 또는 서브 라우트라고 표현하는데 위와 같이 작성하면 주소/posts/:postId와 같이 설정된다. 여기서 문자 앞에 : 를 입력하면 단어 그자체가 아니라 파라미터로서 유저가 명시한 어떤 값을 postId라는 이름으로 받아오는 것이다.

하위 Route 컴포넌트는 상위 Route 컴포넌트에 의해 표시되기 때문에 Outlet 태그를 사용해야한다.

import React from 'react'
import { Outlet } from 'react-router-dom'

function Posts() {
  return (
    <div>
      Posts
      {/* 이 부분에 하위 컴포넌트를 표시하라는 의미가 된다. */}
      <Outlet /> // = 서브 라우트
    </div>
  )
}

export default Posts

Outlet 즉, 서브 라우트 전체에서 공통적으로 사용할 데이터를 전달하려면 context 속성을 사용한다.

// 객체 타입으로
<Outlet context={{ data: 1 }} />

이렇게하면 상위 컴포넌트 안에 있는 어떤 서브 라우트 컴포넌트들에서든 넘겨주는 데이터를 사용할 수 있다.

넘겨주는 속성을 사용하려면 사용 컴포넌트에서 아래 훅을 사용한다.

const context = useOutletContext()
context.data // 1

서브 라우트 방식을 사용하지 않고 단순히 Route를 하나 더 추가하는 방법도 있다.

<Route path="posts" element={<Posts />} />
<Route path='posts/:postId' element={PostDetail} />

따라서, 첫 번째 방식은 공통적으로 표시하고자 하는 내용이 있는 컴포넌트에 작성하는 것이 더 효율적일 것이다.

index route

중첩 형태의 라우트 구조에서 상위 라우트 컴포넌트(<Post />)를 중첩 라우트로 내리고 index 속성을 사용하면 posts 경로로 접근할 때 기본적으로 <Post /> 컴포넌트를 보여주게 되는데 이때, 같은 레벨의 라우트이기 때문에 Outlet을 사용할 필요가 없어진다.

// Before
<Routes>
  <Route path="posts" element={<Post />}>
    <Route path=":postId" element={<PostDetail />} />
  </Route>
</Routes>
// After
<Routes>
  <Route path="posts">
  	<Route index element={<Post />} />
  	<Route path=":postId" element={<PostDetail />} />
  </Route>
</Routes>

그리고 페이지 간에 공통적으로 보여줄 내용들은 Layout이라는 이름의 컴포넌트로 작성한다.

<Routes>
  // 모든 서브 라우트에서 PostLayout 컴포넌트의 내용이 보인다.
  <Route path="posts" element={<PostLayout />}>
    <Route index element={<Post />} />
    <Route path=":postId" element={<PostDetail />} />
  </Route>
</Routes>
function PostLayout() {
  return (
    <div>
      PostLayout은 페이지 간에 공통적으로 보여줄 내용을 다루는 컴포넌트이다.
      <Outlet />
    </div>
  );
}

useParams (URL Parameter)

주소창 값 가져오는 훅 (주소/posts/3)

이 훅을 사용해서 URL path에 명시된 파라미터 값을 받아올 수 있다.

import React from 'react'
import { useParams } from 'react-router-dom'
import { postData } from "../constants/postData";

function PostDetail() {
	// { postId: 유저가 주소에 명시한 값 }
	const params = useParams()
	const post = postData.find((post) => post.id === Number(params.postId));

    return (
      <div>
        <h2>{post.title}</h2>
        <p>{post.body}</p>
      </div>
  );
}

export default PostDetail

주의할 점은 파라미터 객체의 프로퍼티 키 이름은 에서 path에 작성한 입력받을 값을 따른다는 것이다.

<Route path='posts/:postId' element={PostDetail} />
  
const params = useParams() // { postId: 1 }

useSearchParams

주소창 값 가져오는 훅 (주소/posts?title=aaabbbccc)

주소창의 쿼리 스트링을 가져올 수 있다.

검색 파라미터란 주소창에서 ?id=1 과 같이 이루어진 것을 말한다. (쿼리 스트링)
이 훅을 사용해서 좀 더 간편하게 검색 파라미터로 필터링을 할 수 있다.
서버로 요청을 보낼 때가 아닌 자체적으로 필터링할 때 유용하게 사용할 수 있다.

const [searchParams, setSearchParams] = useSearchParams()

설정

// 업데이트 함수에 전달된 객체 값이 쿼리 스트링으로 설정되어 주소창에 표시된다.
setSearchParams({ id: 1 }) // 주소?id=1

가져오기

쿼리 스트링 값을 가져오려면 searchParams state에서 .get() 메서드를 이용한다.
인자로 넣어주는 것은 쿼리스트링과 일치해야 한다.

// 주소?id=1
searchParams.get('id') // 1

// 주소?name='lee'
searchParams.get('name') // lee

입력창에 사용자가 입력하는 값이 쿼리스트링으로 적용되어 이 값에 해당하는 데이터만 보여주는 예시를 만들어 보면

import React, { useEffect, useState } from 'react'
import { Link, Outlet, useSearchParams } from 'react-router-dom'
import { postData } from '../constants/postData'

function Posts() {
  const [searchParams, setSearchParams] = useSearchParams()
  const [posts, setPosts] = useState(postData)

	{/* 2. 입력된 값이 쿼리 스트링으로 설정되고 */}
  const inputChangeHandler = (e) => {
    const title = e.target.value
    title ? setSearchParams({ title }) : setSearchParams({})
  }

  useEffect(() => {
		{/*
		  4. 해당 쿼리 스트링 값과 일치하는 요소들로 이루어진 posts를 만들어
			컴포넌트를 재실행시켜 화면을 업데이트 한다.
		*/}
    setPosts(
      postData.filter((post) => {
        const filter = searchParams.get('title')
        const title = post.title.toLowerCase()

        return filter ? title.includes(filter) : true
      })
    )
	{/* 3. 쿼리 스트링 값이 변경될 때마다 */}
  }, [searchParams])

  return (
    <div>
	  {/* 1. 사용자가 입력창에 제목을 입력하면 */}
      <input type="text" onChange={inputChangeHandler} />
      
      {posts.map((post) => (
        <div key={post.id}>
          <Link to={`/posts/${post.id}`}>{post.title}</Link>
        </div>
      ))}
    </div>
  )
}

export default Posts

url 파라미터와 쿼리 스트링

  • url 파라미터는 주소 경로에 아이디나 이름과 같이 유동적인 값을 넣어서 특정 데이터를 조회하는데 사용된다.
  • 쿼리 스트링은 key=value 형식으로 작성하고 데이터 조회에 필요한 옵션을 전달할 때 사용한다.

useLocation

현재 위치하고 있는 URL과 관련된 객체 정보를 담고 있는 훅

  • pathname : 주소값
  • search : 검색값 (쿼리 파라미터값)
  • state : 주소 이동하면서 전달하고자 하는 값

만약 링크를 타고 이동하면서 데이터를 넘겨주려면 state 프로퍼티를 사용한다.

{posts.map((post) => {
  return (
    <p key={post.id}>
		// 이동시 데이터를 함께 전달한다.
    	<Link to={`/posts/${post.id}`} state={{ post: posts.find((data) => data.id === post.id) }}>{post.title}</Link>
    </p>
  )
})}

위 컴포넌트에서 특정 id의 post를 전달해주기 때문에 아래 컴포넌트에서 전체 데이터를 import 하지 않아도된다.

import React from 'react'
import { useLocation } from 'react-router-dom'

function PostDetail() {
    const location = useLocation()
    const { post } = location.state

    return (
        <div>
            <h3>{post.title}</h3>
            <p>{post.body}</p>
        </div>
    )
}

export default PostDetail

useNavigate

특정 주소로 이동할 수 있게 해주는 훅

const navigate = useNavigate()
  
// 해당 경로로 이동
<button type="button" onClick={() => navigate('/posts')}>이동</button>

// 뒤로가기
<button type="button" onClick={() => navigate(-1)}>이동</button>
  
// 머물렀던 경로를 기억하지 않는다.
<button type="button" onClick={() => navigate(-1, { replace: true })}>이동</button>

{ replace: true } 옵션을 사용하면 머물렀던 페이지가 이력에 남지 않아서 다시 돌아갈 수 없게한다.

  • to
  • className
  • end

링크가 현재 활성인 페이지로 인도했는지 아닌지를 링크에 표시되도록 하기 위해 react-router-dom은 Link 컴포넌트의 대체인 NavLink 컴포넌트를 지원한다.

사용은 Link 대신 NavLink라고 작성하면 되며 기존의 to 속성말고도 다양한 속성을 지원한다.

className을 추가하고 값으로 문자열을 받는게 아닌 대신에 함수를 받는 프로퍼티이다. 이 함수는 a 태그에 추가되어야 하는 css 클래스 이름을 반환한다.

매개변수는 react-router-dom에 의해 자동으로 객체의 isActive 프로퍼티를 받는다.

<NavLink
	to="/home"
	// /home 경로로 이동했으면 isActive 값이 true, 그렇지 않으면 false가 된다.
	className={({ isActive }) =>
	isActive ? true : false
	}
  >
	Go to Home
</NavLink>

이때 주의할 점은 예를 들어 아래와 같이 3개의 경로가 있고 home과 products가 하위 요소라면 home으로 이동하든 products로 이동하든 경로가 /로 시작하기 때문에 항상 Root가 활성 상태라고 인식하게 된다. 따라서, 경로가 정확히 /일 때만을 특정하려면 end 속성을 사용한다.

<NavLink to="/" end>Root</NavLink>
	// /로 시작하기 때문에 상위 링크도 활성화 상태로 인식하게 된다.<NavLink to="/home">Home</NavLink><NavLink to="/products">Products</NavLink>

0개의 댓글