React

[React] react-router-dom v6 에서 Hooks 사용하기

코딩굼벵이 2022. 7. 22. 15:43
728x90

https://react.vlpt.us/react-router/

 

5장. 리액트 라우터 · GitBook

react-router 를 통한 리액트 싱글 페이지 애플리케이션 만들기 SPA 란? Single Page Application (싱글 페이지 어플리케이션) 의 약자입니다. 말 그대로, 페이지가 1개인 어플리케이션이란 뜻입니다. 전통적

react.vlpt.us

 

위 글의 리액트 라우터 사용 과정을 따라가보려 했으나, 해당 글이 react-router-dom v5 기준으로 작성되어 현재 버전인 v6에서 쓰려고 보니 제대로 동작하지 않았다.

그래서 react-router-dom v6에서 업데이트된 Hooks 를 사용해 이전과 달라진 점을 체크하며 과정을 따라해보았다.

* 과정을 따라하는 데에 체크가 필요한 사항들을 위주로 정리했으므로, 모든 변경점을 알고 싶다면 밑의 글을 참고하면 된다.

참고한 글: https://velog.io/@soryeongk/ReactRouterDomV6

 

[React] react-router-dom v6 업그레이드 되면서 달라진 것들

react-router-dom이 v6으로 업그레이드 되었습니다 :) v5와 다른 점이 몇 가지 있으니 꼭 숙지하시기 바랍니다. 안그럼 아무것도 실행되지 않을지도....

velog.io

 

react router v6 공식 문서: https://reactrouter.com/docs/en/v6


cra 및 cd해서 경로 이동 후 react-router-dom 설치

$ yarn add react-router-dom

 

라우터 적용 :: index.js에서 BrowserRouter 라는 컴포넌트를 사용해야 라우터 사용 가능

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

 

Route로 특정 주소에 컴포넌트를 연결하는 방식 비교

v5

import { Switch, Route } from 'react-router-dom';
...
<Switch>
	<Route path="/" exact={true} component={Home}>
    	<Route render={({location}) => (<div>
        	<h2>이 페이지는 존재하지 않습니다: {location.pathname}</h2>
        </div> )}
    	/>
</Switch>

 

- exact를 쓰면 해당 경로와 완전히 똑같을 때만 컴포넌트가 보이고, 쓰지 않으면 경로에 /가 포함된 경우 항상 이 컴포넌트가 함께 보였다.

- 여러 Route를 감쌀 때 Switch를 사용해서 규칙이 일치하는 라우트 하나만 렌더링 시켜줄 수 있었다.

- path를 따로 지정해주지 않으면 아무것도 일치하지 않을 때 보여줄 Not Found 페이지도 구현 가능했다.

 

v6

import { Route, Routes, useLocation } from 'react-router-dom';

const location = useLocation();
return (
...
    <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/*" element={<div>
          <h2>이 페이지는 존재하지 않습니다: {location.pathname}</h2>
        </div>}
        />
    </Routes>
...
)

 

- component 대신 element를 쓰도록 변경됐다. element 내부에 들어가는 것도 컴포넌트를 부르는 형태

- render 도 삭제됐다. element 안에 직접 집어넣으면 됨.

- exact 옵션이 삭제됐다. 여러 라우팅을 매칭하고 싶은 경우 URL 뒤에 * 을 사용한다.

- path를 기존에는 절대경로로 지정했으나, 상대경로로 지정할 수 있게 됐다.

 

파라미터

v5

const Profile = ({ match }) => {
  const { username } = match.params;
  const profile = profileData[username];
  
  return (
    <div>
      <h3>
        {username}({profile.name})
      </h3>
      <p>{profile.description}</p>
    </div>
  );
};

 

- match 객체 안의 params 값을 사용했다. 더 정확히 말하자면, App 쪽 Route path에서 "/profiles/:username"의 :username을 넣어준 params 값을 match props로 전달받아 사용했다.

 

v6

import { useParams } from 'react-router-dom';

const { username } = useParams();
const profile = profileData[username];

 

- match에 접근할 필요없이 useParams를 사용해 params를 바로 불러올 수 있다.

 

쿼리

v5 : qs 라이브러리를 깔아서 사용했다.

v6 : react-router-dom 라이브러리의 useSearchParams 사용 가능

import { useSearchParams } from 'react-router-dom';

const [searchParams] = useSearchParams();
const detail = searchParams.get('detail') === 'true';

 

서브 라우트(중첩 라우트)

라우트 내부의 라우트를 만드는 것. v6 에서는 2가지 방법이 있다.

1. 상위 라우터에서 중첩 라우터를 사용하고, 중첩 라우터에서 Outlet 컴포넌트를 사용한다.

2. 중첩 라우터에 곧바로 기재한다.

벨로퍼트가 사용한 예제는 2번에 더 가까워 보인다. v5를 v6 방식으로 바꿔보았다.

// Profiles.js
import { NavLink, Route, Routes } from 'react-router-dom';

const activeStyle = {
    background: 'black', color: 'white'
}
...
            <ul>
                <li>
                    <NavLink
                        to="/profiles/yejin"
                        style={{ background: 'blue', color: 'white' }}
                    >
                        yejin
                    </NavLink>
                </li>
                <li>
                    <NavLink
                        to="yeonju"
                        style={activeStyle}
                    >
                        yeonju
                    </NavLink>
                </li>
            </ul>

            <Routes>
            	// /profiles 경로일 때만 보이는 JSX
                <Route path="/" element={<div>유저를 선택해주세요.</div>} />
				// /profiles/... 경로일 때 보이는 컴포넌트
                <Route path=":username" element={<Profile />} />
            </Routes>
// App.js 라우팅 부분
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path='/*' element={<div>
          <h2>이 페이지는 존재하지 않습니다: {location.pathname}</h2>
        </div>}
        />
        <Route path="/about" element={<About />} />
        <Route path="/profiles/*" element={<Profiles />} />
        <Route path="/history" element={<HistorySample />} />
      </Routes>

 

- 절대경로가 아닌 상대경로도 사용할 수 있게 되었으므로 상위 라우터에서 정해준 경로 뒷부분만 적으면 된다.
  즉, 상위 라우터에서 `/profiles/*` 로 라우팅해준 컴포넌트에서는 to나 path에 `/profiles/asdf` 을 적는 것과 `asdf`만 적는 것이 같다.

- activeClassName, activeStyle props 제거 - 대신 style과 className에 함수를 전달할 수 있게 됐다.

- <NavLink exact> 는 <NavLink end>로 변경되었다고 한다.

- <Link>의 component prop이 제거되어 더이상 지원하지 않는다.

 

history, location, match props 제거 - Hooks 사용

v5 v6
history 또는 useHistory useNavigate
location useLocation
match.params useParams

* v5 에서는 라우트로 사용된 컴포넌트에 해당 객체들이 props로 들어왔지만,  v6 에서는 Hooks로 사용할 수 있게 되었다.

* useParams 는 url에서 받아오는 string을, useLocation은 객체를 넘겨주는 듯 하다.

v5 - history 객체

function HistorySample({ history }) {
  const goBack = () => {
    history.goBack();
  };

  const goHome = () => {
    history.push('/');
  };

  useEffect(() => {
    console.log(history);
    const unblock = history.block('정말 떠나실건가요?');
    return () => {
      unblock();
    };
  }, [history]);
  ...
}

 

v6 - useNavigate

import { useNavigate } from 'react-router-dom';

const navigate = useNavigate();

const goBack = () => {
    const confirm = window.confirm('정말 떠나시겠어요?');
    if (confirm) {
        navigate(-1);
    }
};
const goHome = () => {
    navigate('/');
};

 

- history.push 와 history.replace 모두 navigate라는 명칭으로 사용한다. replace 기능은 `navigate(to, {replace, true})`의 형태로 사용 가능하다. state를 사용하면 `navigate(to, {state})` 의 형태로 사용 가능하다. (to에는 경로를 넣으면 된다)

- history나 useHistory에서는 `go, goBack, goForward` 였던 것들이 v6에서는 navigate로 통일되어 index를 넣는 것으로 해결할 수 있다.

 

* 한꺼번에 보기

v5

import React from 'react';
import { withRouter } from 'react-router-dom';
const WithRouterSample = ({ location, match, history }) => {
  return (
    <div>
      <h4>location</h4>
      <textarea value={JSON.stringify(location, null, 2)} readOnly />
      <h4>match</h4>
      <textarea value={JSON.stringify(match, null, 2)} readOnly />
      <button onClick={() => history.push('/')}>홈으로</button>
    </div>
  );
};

 

v6

import { useParams, useLocation, useNavigate } from 'react-router-dom';

const WithRouterSample = () => {
    const params = useParams(); // url에 대한 정보
    const location = useLocation(); // 현재 페이지에 대한 정보
    const navigate = useNavigate();

    return (
        <div>
            <h4>params</h4>
            <textarea value={JSON.stringify(params)} readOnly />
            <h4>location</h4>
            <textarea value={JSON.stringify(location, null, 2)} readOnly />
            <h4>navigate</h4>
            <button onClick={() => navigate(-1)}>홈으로</button>
        </div>
    )
}