tech.junha.kr

[React Router] 라우터 사용하기(react-router-dom v5)

1. SPA와 라우터의 관계

• SPA는 서버에 하나의 페이지만 요청하여 전체 웹앱을 사용하는데, 이때 리액트 라우터는 CSR, AJAX 기술을 활용하여, 페이지 이동 시 리로드 없이 라우팅할 수 있도록 한다.

• MPA와 다르게, 여러 페이지를 하나의 앱의 구성요소로 보고, 여러 페이지 간의 스타일, 컴포넌트를 재활용하여 구현한다.

• 자바스크립트만을 활용해 전체 페이지를 만들기에, 첫 요청 시 빈 페이지를 받게 된며, 서버로 부터 데이터만 받아온다.

SPA의 기술적 장점

• 서버에서 페이지를(서버 랜더링) 만들 필요가 없으므로 CDN에 캐싱이 가능하다.

• 매번 페이지 요청을 할 필요가 없어 네트워크 요청이 줄어든다.

• 마찬가지로 데이터 요청 등을 캐싱하여 재사용하는 등 제약 조건이 줄어든다.

• 웹사이트를 개별 페이지보다는 하나의 앱으로 보는 설계로 컴포넌 재사용, 라우팅, 훅을 통한 로직등 고도의 소프트웨어 설계와 패턴을 적용할 수 있다.

SPA의 기술적 난관들

• MPA방식 보다는 Search Engine Optimization에 불리하다.

• 하나의 자바스크립트 앱이 지속하므로, 데이터 활용과 메모리 성능 관리가 필요하다.

• 여러 페이지를 전송받는 것 보다, 하나의 거대한 자바스크립트 앱을 전송받아야 하므로 코드가 많아질수록 로드 속도가 느려진다. 따라서 코드 스플릿팅, 레이지 로딩 등을 통해 보완을 해야한다.

MPA 란?

• Multi Page Application은 서버에 미리 여러 페이지를 두고, 유저가 네비게이션 시 요청에 적합한 페이지를 전달한다.

• 미리 서버에서 전체 페이지를 빌드해 브라우저로 전송된다.

• 서버에 라우팅을 처리하는 기능이 있고, 서버에서 여러 페이지를 관리한다.

• 페이지 요청마다 모든 리소스를 다시 받아오므로, 페이지 간 데이터를 재활용하기 힘들다.

SPA에서의 라우팅

• 주로 HTML5의 History API (혹은 URL Hash)를 이용해 페이지 리로드 없는 페이지 전환을 구현한다

• hashRouter: URL에 #가 붙는다. 하지만 github page같은 곳에서 빌드할 때 유리하다

• history, location 등 HTML5 API를 활용한다.

• visibilitychange, popstate, beforeunload 등 window event를 활용하여 페이지 전환 이벤트 시 핸들러를 등록하여 활용한다.

• react-router, reach-router 등의 라이브러리를 활용하면, 라우팅 관련 기능을 쉽게 사용할 수 있다.

2. react-router

• React의 JSX를 이용하거나, History API를 사용하여 라우팅을 구현한다.

• Declarative routing 방법: JSX:

• Imperative routing 방법: History API: push('/path')

• 웹에서는 react-router-dom을 사용한다. (react-router-native)

• 적용 시, 서버의 모든 path에서 같은 앱을 서빙하도록 해야 한다.

3. react-router-dom 사용하기

• 로 감싸 Router Context를 제공해야 한다.

• Route로 path를 정의하고, 그 안에 렌더링하고자 하는 컴포넌트를 넣는다.

• Link로 특정 페이지로 이동 시, 리로드 없이 페이지가 이동한다.

• Switch로, 매칭되는 라우트 하나를 렌더링하게 한다.

1) BrowserRouter

• HTML5의 History API를 사용하여, UI와 URL의 싱크를 맞추는 역할을 한다.

• 모든 URL에 대해 동작하게 하기 위해서는 서버 설정이 필요하다.

• 모든 path 앞의 basename을 지정할 수 있다. ex) basename="/ko" 다국어지원

• forceRefresh로, 페이지 이동 시 리프레시할 것인지 지정할 수 있다.

<BrowserRouter>
	<Switch>
    	<Route path="/about"><AboutPage/></Route>
        <Route path="/contact"><ContacPage/></Route>
        <Route path="/"><HomePage/></Route>
    </Switch>
</BrowserRouter>

• Link 컴포넌트는 클릭하면 다른 주소로 이동시킨다.
• 리액트 라우터를 사용할땐 태그 대신 컴포넌트를 사용해야한다. a 태그의 기본적인 속성은 페이지를 이동시키면서, 페이지를 아예 새로 불러오기 때문에, 리액트 앱이 지니고있는 상태들도 초기화되고, 렌더링된 컴포넌트도 모두 사라지고 새로 렌더링을 하게된다.

• 컴포넌트는 HTML5 History API 를 사용하여 브라우저의 주소만 바꿀뿐, 페이지를 새로 불러오지는 않는다.

• to prop을 특정 URL로 받아, 클릭 시 네비게이션 한다.

• NavLink의 경우, 매칭 시 어떤 스타일을 가질지 등의 추가 기능이 있다. (현재 path에 따라 스타일 적용 가능)

• to에 location object나 함수를 받을 수 있다.

Redirect

• Link와 비슷하나, 렌더링되면 to prop으로 지정한 path로 이동한다.

• Switch 안에서 쓰일 경우, from, to를 받아 이동하게 만듦. ex) from="/" to="/login"

<Link to="/about"><aboutPage></Link>

3) Route

<BrowserRouter>
      <Navigation />
      <Route path="/" exact={true}><Home /></Route>
      <Route path="/about" component={About} />
      <Route path="/movie/:id" render={(props) => props.id > 10 ? <Deatails> : ""} />
</BrowserRouter>
  • 라우터는 상위 path가 있으면 호출되기 때문에 중복된다.
  • path와 컴포넌트를 매칭한다.
  • 매칭되는 컴포넌트는 children으로 넣어주거나, component prop으로 넘긴다.
  • render prop으로 매칭되었을 때 실제 어떤 컴포넌트를 랜더링할지 통제할 수 있다.
  • Router로 랜더링 되는 최상위 컴포넌트는 match, location, history를 prop으로 받는다.

4) switch

• 여러 Route 중 매치되는 Route 위에서부터 하나를 선택하여 렌더링한다.

• 매칭되는 Route가 없으면 아무것도 보여주지 않는다. fallback용으로 404 Not Found Page를 맨 아래에 배치할 수 있다

• path="/"의 경우 모든 path에 매칭되므로 exact 키워드를 추가하거나 가장 아래로 내린다.

<Switch>
  <Route exact path='/'>
    <Home mbtiArray={mbtiArray} />
  </Route>
  <Route path='/chat'>
    <Chat userObj={userObj} />
  </Route>
  <Route path='/profile'>
    <Profile userObj={userObj} typeInit={typeInit} />
  </Route>
  <Route>
    <Redirect to='/' />
  </Route>
</Switch>;

5. useHistory, useLocation, useParams, useRouteMatch

• 최상위 컴포넌트가 아니더라도, hook으로 react-router 관련 객체에 접근할 수 있음.

• history, location, params, match 객체에 접근함.

6. Private Router

• 특정 조건이 충족되지 않았을 때, 다른 페이지로 Redirect 하도록 하는 기능이다.
• 유저의 상세 페이지, 개인정보 변경 페이지 등을 이동하려고 할 때 사용한다.

Declartive 방법

function PrivateRoute({ component: Component, ...props }) {
  return (
    <Route
      {...props}
      render={(props) => {
        const isLoggedIn = !!getUserInfo();
        if (!isLoggedIn) {
          return <Redirect to='/login' />;
        }
        return <Component {...props} />;
      }}
    ></Route>
  );
}

Imperative 방법

function usePrivateRoute(validateFunc) {
  //  imperative
  const history = useHistory();
  useEffect(() => {
    if (!validateFunc()) {
      history.push('/login');
    }
  }, []);
}

6. query string

function App() {
  ...
  return (
    <Link to='/contact?email=example@example.com&address=Seoul'>Contact</Link>
  );
}
function ContactPage() {
  const location = useLocation();
  const searchParams = new URLSearchParams(location.search);
  
  const email = searchParams.get('email'); // query의 key값을 이용
  const address = searchParams.get('address');
  
  return (
    <PageLayout header='Contact Page'>
      <em>{email}</em>
      <br />
      <strong>{address}</strong>
    </PageLayout>
  );
}

• URL의 query string 정보를 활용해 앱을 구성할 수 있다.

• URLSearchParamsAPI, useLocation을 활용한다.