리액트 라우터(React Router) - BrowserRouter, HashRouter, Route, Switch, Link (Feat. SPA)

Noma·2021년 4월 20일
5

1. 싱글 페이지 어플리케이션

리액트는 싱글 페이지 어플리케이션을 쉽게 만들 수 있도록 하는 라이브러리이다. 싱글페이지에는 아래와 같은 문제점이 있는데,

  • 각 페이지를 북마크 할 수 없음
  • 브라우저상에서 뒤로 가기, 앞으로 가기와 같은 내비게이션 사용 불가

이를 보완하기 위해서는 화면들에 따라 주소를 각각 만들어, 다른 주소에 다른 뷰를 보여줘야 한다(Routing). 하지만 리액트 자체에는 이러한 기능이 내장되어 있지 않으므로, 관련 라이브러리를 설치해야 한다.

그 중 가장 역사가 오래되고 많이 쓰이는, 리액트 라우터를 사용해보자.

2. React Router란?

React Router는 써드파티 라이브러리로, 여러 화면으로 구성된 웹 어플리케이션을 만드는 데 사용되고 있다. 이는 클라이언트 사이드에서 이뤄지는 라우팅을 간단하게 해주며, 서버 사이드 렌더링을 도와주는 도구들도 함께 딸려온다. 추가적으로 이는 react-native에서도 사용할 수 있다.

2.1 시작하기

리액트 라우터를 시작하려면, 먼저 웹 앱을 만들어야 한다. 웹 앱은 Create React App을 사용해서 만드는 것이 추천되므로,

npx create-react-app router-basic
cd router-basic

위와 같이 새로운 프로젝트를 만들어 보자.

2.2 설치

다음으로 공용 npm 레지스트리에서 npm 또는 yarn으로 React Router를 설치할 수 있다.

npm install react-router-dom // 둘 중 하나만 사용해서 설치
yarn add react-router-dom

🔍 참고
버전업(v4)이 되면서 react-router라는 코어 모듈에 react-router-dom, react-router-native 등이 추가되었다.

  • react-router-dom : react-router모듈에 dom이 바인딩 되어 있으며, 웹개발자를 위한 모듈이다.
  • react-router-native : 이름에서도 알 수 있듯이 react-native를 개발할 때 사용하는 모듈이다.

3. 실습하기

src 안에 component 폴더를 추가해 home.jsx와 about.jsx를 만들고,

//home.jsx
import React from 'react';

const Home = (props) => (
    <div>
        <h1>Home</h1>
        <p>이 곳은 홈 화면입니다.</p>
    </div>
);
export default Home;

//about.jsx
import React from 'react';

const About = (props) => (
    <div>
        <h1>About</h1>
        <p>라우터 뿌시기</p>
    </div>
);
export default About;

<BrowserRouter>를 사용해 app.jsx와 index.js를 아래와 같이 바꿔보자.

//index.js
import { BrowserRouter } from 'react-router-dom';

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

//app.jsx
import React from 'react';
import About from './components/about';
import Home from './components/home';

function App() {
  return (
    <>
      <Home />
      <About />
    </>;
  );
}
export default App;

3.1 <BrowserRouter>

BrowserRouter는 HTML5의 History API(pushState, replaceState, popstate event)를 사용하여 URL과 UI를 동기해주는 <Router>이다.

이는 페이지를 새로고침하지 않고도 주소를 변경할 수 있도록 해주고, 현재 주소에 관련된 정보를 props로 조회 및 사용이 가능하도록 한다.

BrowserRouter는 리액트 라우터 돔을 적용하고 싶은 컴포넌트의 최상위 컴포넌트를 감싸주는 래퍼 컴포넌트이기 때문에, 여기서는 App 컴포넌트를 감싸주면 된다.

🔍참고 : HashRouter
BrowserRouter는 우리가 서비스하는 웹 애플리케이션의 웹서버 세팅이 사용자가 어떤 path로 들어와도 동일한 웹페이지(루트 페이지에 있는 html 파일을)를 서비스 할 수 있어야 사용할 수 있다.

그게 안되는 경우, BrowserRouter 대신 HashRouter를 써야한다.

주소창에 #이 있는 것을 볼 수 있다. 웹서버는 이 #이하의 것을 무시하고, 자바스크립트를 이용해 리액트 라우터 돔이 저 정보를 가져와 해당하는 컴포넌트를 라우팅 해주게 된다. 보다시피 주소가 깔끔하진 않으므로 BrowserRouter를 쓸 수 있는 경우 그것을 쓰도록 하자.

다시 실습으로 돌아가 해당 코드를 출력해보면,

메인에 Home, About 컴포넌트가 모두 보여지는 것을 알 수 있다.

이를 localhost:3000에선 Home이, localhost:3000/about에선 About이 렌더링되게 하려면 어떻게 해야할까?

3.2 <Route>

App.jsx를 다음과 같이 바꿔보자.

import React from 'react';
import { Route } from 'react-router';
import About from './components/about';
import Home from './components/home';

function App() {
  return (
    <>
      <Route path="/">
        <Home />
      </Route>
      <Route path="/about">
        <About />
      </Route>
    </>
  );
}
export default App;

Route는 path에 따라 해당 UI를 보여주는 라우팅 기능을 가진 컴포넌트 로, path=""부분에 URL 경로를 적고, 렌더링될 컴포넌트를 자식요소로 넣어주면 된다.

<Route path="/이동할 주소">
	<출력할 컴포넌트 />
<Route/>

🔍 참고
위 방법 외에도 Route 렌더링 메소드가 3가지 더 존재하는데, 이는 다음 포스팅에서 다루도록 하겠습니다.

  • 여러 개의 주소를 지정하고 싶으면, path={['/about','/profile']}와 같이 경로 배열을 지정해주면 된다. 배열은 js 코드이므로 {}로 묶어주는 것을 잊지 말자.

결과값을 보면 다음과 같다.

localhost:3000/에서는 Home 컴포넌트만 잘 뜨지만 localhost:3000/about에서는 About 뿐만 아니라 Home 컴포넌트도 출력된 것을 볼 수 있다.

왜 이렇게 나올까?

/about은 Home에 전달한 것과 동일한 /이 포함되어져 있기 때문에(즉 Home은 /를, About은 //about path를 가진다고 여겨져), 둘다 출력된 것이다. 이러한 경우엔 Home쪽의 Route에 exact라는 옵션을 주어 해결이 가능하다.

3.2.1 exact

<Route path="/이동할 주소" exact>
  <출력할 컴포넌트 />
</Route>

exact의 의미는 정확히 이 path에서만 해당 라우팅이 이뤄지도록 하라는 명령이다. 원래는 exact={true}가 맞지만, true는 생략해도 된다.

그러면 아래와 같이 /about에서는 About 컴포넌트만 출력되는 것을 볼 수 있다.

3.3 <Switch>

위와 비슷한 효과를 내는 데에는, Switch로 감싸주는 방법도 있다.

import React from 'react';
import { Route, Switch } from 'react-router';
import About from './components/about';
import Home from './components/home';

function App() {
  return (
    <>
      <Switch>
        <Route path="/">
          <Home />
        </Route>
        <Route path="/about">
          <About />
        </Route>
      </Switch>
    </>
  );
}
export default App;

하지만 결과는 주소가 /이어도 /about 이어도 Home 컴포넌트만 보여지는 것을 알 수 있다.

이는 Switch가 감싸준 Route들 중에 Route path와 일치하는 첫 번째 컴포넌트만을 라우팅 해주기 때문이다. 즉 /about이어도 /에서 먼저 걸리기 때문에 Home이 출력되게 된다.

이럴 경우 <Route path="/" exact>처럼 path/인 라우트에 exact를 부여하거나, path/인 라우트와 /about인 라우트의 순서를 바꿔주면 된다.

//(생략..) 방법1. exact부여
function App() {
  return (
    <>
      <Switch>
        <Route path="/" exact>
          <Home />
        </Route>
        <Route path="/about">
          <About />
        </Route>
      </Switch>
    </>
  );
}
export default App;
//(생략..) 방법2. 순서 변경
function App() {
  return (
    <>
      <Switch>
        <Route path="/about">
          <About />
        </Route>
        <Route path="/" exact>
          <Home />
        </Route>
      </Switch>
    </>
  );
}
export default App;

그 결과 /about을 주소창에 치면 About이 잘나오는 것을 볼 수 있다.

이처럼, path와 일치하는 첫 번째 컴포넌트만을 라우팅 해주는 Switch의 성질을 잘 활용하면, 사용자가 존재하지 않는 URL로 접근했을 때 Not Found 페이지를 보여주게 할 수 있다.

3.4 Not Found

//(생략..) 
function App() {
  return (
    <>
      <Switch>
        <Route path="/" exact>
          <Home />
        </Route>
        <Route path="/about">
          <About />
        </Route>
        <Route path="/">
          Not Found
        </Route>
      </Switch>
    </>
  );
}
export default App;

/정확히 매칭되는 경우엔 Home을 보여주고, /about일 땐 About을, /asdfas과 같이 존재하지 않는 path는 마지막 /에 걸려 Not Found를 보여주게 된다.

그런데 Route를 사용하면, 주소창에 매번 /about과 같이 URL을 직접 입력해야지만 About 페이지로 넘어갈 수 있다. 이는 매우 번거롭게 느껴지는데,a태그를 추가해 링크로 이동하도록 하면 어떨까?

<ul>
  <li>
    <a href="/">Home</a>
  </li>
  <li>
    <a href="/about">About</a>
  </li>
</ul>

결과는 잘 이동된다. 하지만 이 방법은 절대 쓰면 안되는 방법이다.

우리가 싱글페이지 어플리케이션을 만듬에 있어 중요한 점은,

페이지가 리로드 되지 않고 현재 페이지에서의 상태를 유지한 채, 주소에 따라 해당 화면을 보여주어야 한다는 점이다.

하지만 이렇게 a태그를 쓰게 되면 페이지가 완전히 새로 로딩되게 된다.
현재 상태와 기존의 컴포넌트를 날리고 다시 처음부터 렌더링하게 된다는 말이다.

그러면 어떻게 해야할까? 이럴때 쓰는 것이 Link 컴포넌트이다.

<Link to="이동할주소">내용</Link>

Link 컴포넌트는 클릭하면 다른 주소로 이동하는 과정에서 페이지를 새로 불러오지 않고 기존의 상태를 유지한 채 History API만을 사용해서 페이지의 주소만 변경해준다. 즉 a태그와 비슷하지만 추가로 페이지 전환을 방지하는 기능이 들어있다.

Link를 이용해 app.jsx를 바꿔주면,

//app.jsx
import React from 'react';
import { Route } from 'react-router';
import { Link } from 'react-router-dom';
import About from './components/about';
import Home from './components/home';

function App() {
  return (
    <>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
      </ul>
      <Switch>
        <Route path="/" exact>
          <Home />
        </Route>
        <Route path="/about">
          <About />
        </Route>
        <Route path="/">
          Not Found
        </Route>
      </Switch>
    </>
  );
}

export default App;

다음과 같이 Home을 누르면 localhost:3000으로, About을 누르면 localhost:3000/about으로 주소가 변경되고, Route가 그 주소에 해당하는 컴포넌트를 출력해준다.

다음 포스팅과 이어집니다.

📚 Reference

profile
Frontend Web/App Engineer

2개의 댓글

comment-user-thumbnail
2022년 7월 13일

router 관련 글 이것저것 찾아봤는데 제일 명확하네요! 감사합니다!

1개의 답글