본문 바로가기

Computer Programming/[리액트를 다루는 기술]

[리액트를 다루는 기술] 13. 리액트 라우터 (URL /:, 쿼리스트링, 라우트 중첩, useNavigate, Navigate)

기존에 띄웠던 웹 애플리케이션을 그대로 유지하면서 라우팅 설정에 따라 또 다른 페이지를 보여주는 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

[리액트를 다루는 기술] 김민준