React Router

나연·2020년 6월 5일
1

React

목록 보기
1/1
post-thumbnail

설치

install npm react-router-dom 로 설치해주고 사용할 파일에 import 한다.

🐱BrowserRouter

react-router-dom을 적용하고 싶은 컴포넌트의 최상위 컴포넌트에 감싸주는 Wrapper 컴포넌트로서의 역할을 한다.

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

...생략

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

위의 모습처럼 최상위 컴포넌트인 App컴포넌트를 감싸주면 App컴포넌트는 BrowserRouter를 사용할 수 있는 상태가 된다.

🐭Route

router의 가장 본질적인 기능인 url path에 따른 분류

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter, Route } from 'react-router-dom';

function Home(){
  return (
    <div>
      <h2>Home</h2>
      Home...
    </div>
  )
}

function Topics(){
  return(
    <div>
      <h2>Topics</h2>
      Topics...
    </div>
  )
}

function Contact(){
  return(
    <div>
      <h2>Contact</h2>
      Contact...
    </div>
  )
}


function App(){
  return (
    <div>
    <h1>React Router DOM</h1>
    <ul>
      <li><a href='/'>Home</a></li>
      <li><a href='/topics'>Topics</a></li>
      <li><a href='/contact'>Contact</a></li>
    </ul>
    <Route path='/'><Home></Home></Route>
    <Route path='/topics'><Topics></Topics></Route>
    <Route path='/contact'><Contact></Contact></Route>
    </div>
    )
}


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

우리는<Route>라는 태그로 컴포넌트를 감쌌기 때문에 해당 컴포넌트를 원하는 url에 따라 분류할 수 있다. '/'에서는 Home컴포넌트가, '/topics'에서는 Topics컴포넌트가, '/contact'에서는 Contact 컴포넌트가 나오겠지??

그런데 기이한 현상이 생긴다.

지정한 대로 '/topics'라는 주소인데 Home 컴포넌트도 같이 보여진다. '/contact'라는 주소에 가도 Home 컴포넌트가 같이 보여진다.

http://localhost:3001/ 의 path는 '/'인게 맞지만
http://localhost:3001/topics 의 path는 '/'이면서 '/topics'에도 해당하여 생긴 현상이다.

해결하는 방법이 두가지 있다. exactSwitch이다.

exact 속성

정확히 path가 일치하는 경우에만 매칭시켜준다.

function App(){
  return (
    <div>
        <h1>React Router DOM</h1>
        <ul>
          <li><a href='/'>Home</a></li>
          <li><a href='/'>Home</a></li>
          <li><a href='/topics'>Topics</a></li>
          <li><a href='/contact'>Contact</a></li>
        </ul>
        <Route exact path='/'><Home></Home></Route>  //--> 이부분에 exact를 추가
        <Route path='/topics'><Topics></Topics></Route>
        <Route path='/contact'><Contact></Contact></Route>
    </div>
    )
}

이제 Home 컴포넌트는 정확히 path가 '/'일 때만 나오게 된다.

🐨Switch

Switch라는 컴포넌트로 Route를 감싸주면 react-router-dom은 path와 일치하는 첫번째 컴포넌트가 발견되면 나머지 컴포넌트는 버린다.

import { BrowserRouter, Route, Switch } from 'react-router-dom';

...생략

function App(){
  return (
    <div>
        <h1>React Router DOM</h1>
        <ul>
          <li><a href='/'>Home</a></li>
          <li><a href='/'>Home</a></li>
          <li><a href='/topics'>Topics</a></li>
          <li><a href='/contact'>Contact</a></li>
        </ul>
        <Switch>
           // <Route path='/'><Home></Home></Route>  --> 어떤 path로 가든 Home컴포넌트만 출력된다. 
            <Route path='/topics'><Topics></Topics></Route>
            <Route path='/contact'><Contact></Contact></Route>
   	    <Route path='/'><Home></Home></Route> //--> 젤 마지막으로 가면 모두 일치하는 path에 맞게 작동
        </Switch>
    </div>
    )
}

혹은 exact를 사용하는 방법도 있다.

<Switch>
    <Route exact path='/'><Home></Home></Route>
    <Route path='/topics'><Topics></Topics></Route>
    <Route path='/contact'><Contact></Contact></Route>
    <Route path='/'><Home>Not Found</Route>
</Switch>

🐸Link

위에 작성한 코드들은 a태그로 작성했기 때문에 링크를 이동할 때마다 페이지가 완전히 새롭게 리로드된다. 링크의 이동 시에 전체 페이지를 리로드하는 대신 변화된 부분만 보여줄 있는 방법이 없을까에 대한 고민을 해결해주는 것이 바로 Link 기능이다.

import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';

...생략

function App() {
  return (
    <div>
      <h1>React Router DOM</h1>
      <ul>
        <li><Link to='/'>Home</Link></li> //--> a태그를 Link로 바꿔주면 해결
        <li><Link to='/topics'>Topics</Link></li>
        <li><Link to='/contact'>Contact</Link></li>
      </ul>
      <Switch>
        <Route exact path='/'><Home></Home></Route>
        <Route path='/topics'><Topics></Topics></Route>
        <Route path='/contact'><Contact></Contact></Route>
        <Route path='/'>Not found</Route>
      </Switch>
    </div>
  )
}

🐻NavLink

NavLink는 Link와 유사하지만 조금 더 기능이 추가된 것이다. 우선 Link로 작성한 태그를 모두 NavLink로 바꾸고 변화를 살펴보자.

import { BrowserRouter, Route, Switch, Link. NavLink } from 'react-router-dom';

function App() {
  return (
    <div>
      <h1>React Router DOM</h1>
      <ul>
        <li><NavLink exact to='/'>Home</NavLink></li> //Link를 모두 NavLink로 바꿔주면?
        <li><NavLink to='/topics'>Topics</NavLink></li>
        <li><NavLink to='/contact'>Contact</NavLink></li>
      </ul>
      <Switch>
        <Route exact path='/'><Home></Home></Route>
        <Route path='/topics'><Topics></Topics></Route>
        <Route path='/contact'><Contact></Contact></Route>
        <Route path='/'>Not found</Route>
      </Switch>
    </div>
  )
}

NavLink로 바꾸고나서 생긴 변화는 현재 위치한 url에 active라고 하는 class가 생긴다는 것이다.

이 예시처럼 contact를 눌렀을 때 contact에 active라고하는 class가 부여된 것을 확인할 수 있다. 사용자가 현재 어떤 path에 있는지 조금 더 직관적으로 확인할 수 있으며 해당 class에만 css를 줘서 메뉴를 디자인할 수도 있다.

🐰Nested Routing

이번엔 Topics 를 클릭했을 때 또 다른 여러 Topic에 대한 링크들이 나오도록 만들어보자.

function Topics() {
  return (
    <div>
      <h2>Topics</h2>
      <ul>
        <li><NavLink to="/topics/1">HTML</NavLink></li>
        <li><NavLink to="/topics/2">JS</NavLink></li>
        <li><NavLink to="/topics/3">React</NavLink></li>
      </ul>

      <Switch>
        <Route path="/topics/1">
          HTML is ...
          </Route>
        <Route path="/topics/2">
          JS is ...
          </Route>
        <Route path="/topics/3">
          React is ...
          </Route>
      </Switch>
    </div>
  )
}

아까 만든 Topics 함수에 또 새로운 라우터를 지정해서 라우터 안에 라우터가 동작하게끔 만들었다.

이렇게 Topics의 첫번째 topic 링크로 이동이 가능하다.

🐮Parameter

그런데 만약에 우리가 가지고 있는 topic이 단 세개가 아니라 여러개라면? 하나하나 일일이 path의 숫자를 늘려주는 것이 번거로워진다. 그래서 자동으로 path가 만들어지도록 하는 함수를 짜보자.

let contents = [
  { id: 1, title: 'HTML', description: 'HTML is...' },
  { id: 2, title: 'JS', description: 'JS is...' },
  { id: 3, title: 'React', description: 'React is...' }
]

function Topics() {
  return (
    <div>
      <h2>Topics</h2>
      <ul>
        {contents.map(content =>
          <li><NavLink to={`/topics/${content.id}`}>{content.title}</NavLink></li>
        )}
      </ul>

      <Switch>
        <Route path="/topics/1">
          HTML is ...
          </Route>
        <Route path="/topics/2">
          JS is ...
          </Route>
        <Route path="/topics/3">
          React is ...
          </Route>
      </Switch>
    </div>
  )
}

우리의 컨텐츠가 contents라는 배열에 담겨있다고 가정해보자. 해당 배열의 내용이 몇개가 되든 map을 이용하여 자동으로 하나씩 NavLink를 만들어주는 것을 확인할 수 있다.
이번엔 Route태그를 하나로 줄이고 싶다. 현재 Route의 path에서 다른 부분은 topics 뒤의 숫자 뿐이다. 그렇다면 이 문자를 판별해주는 기능이 없을까?

🐷useParams

Route path='/contact/:id' contact 뒤에 붙어있는 :id가 뭔지 살펴보자.
contact 뒤의 :는 뒤에 어떤 값이 있다는 것을 알려주는 라우트다.

import { BrowserRouter, Route, Switch, Link, NavLink, useParams } from 'react-router-dom';

...생략

let contents = [
  { id: 1, title: 'HTML', description: 'HTML is...' },
  { id: 2, title: 'JS', description: 'JS is...' },
  { id: 3, title: 'React', description: 'React is...' }
]

function Topic() {
  let id = useParams(); //useParams를 사용후 
  console.log(id) //출력하면 ? 
  return (
    <div>
      <h3>Topic</h3>
      Topic...
    </div>
  )
}

function Topics() {
  return (
    <div>
      <h2>Topics</h2>
      <ul>
        {contents.map(content =>
          <li><NavLink to={`/topics/${content.id}`} >{content.title}</NavLink></li>
        )}
      </ul>

      <Route path='/topics/:id'>
        <Topic></Topic>
      </Route>
    </div>
  )
}

Topics에서 각각의 id값으로 path가 부여된 링크들을 클릭하고 그것을 :id라는 파라미터로 라우팅 한 후에 useParams를 이용해 출력해주었다. 어떤 값이 출력될까? 바로 topics 뒤에오는 path의 값이 그대로 출력된다. 우리는 url에 온 값을 useParams를 이용해 그대로 가져올 수 있다는 의미이다.


function Topic() {
  let params = useParams();
  let selected_topic = { //기본값 설정 
    title: 'Sorry',
    description: 'Not Found'
  } 
  contents.forEach(content=>{
  if(content.id===Number(params.id)){
    selected_topic = content
    }
  })
  return (
    <div>
      <h3>{selected_topic.title}</h3>
      {selected_topic.description}
    </div>
  )
}

function Topics() {
  return (
    <div>
      <h2>Topics</h2>
      <ul>
        {contents.map(content =>
          <li><NavLink to={`/topics/${content.id}`} >{content.title}</NavLink></li>
        )}
      </ul>

      <Route path='/topics/:id'>
        <Topic></Topic>
      </Route>
    </div>
  )
}

우리는 이렇게 Route를 하나하나씩 생성하지 않아도, : 라는 기호로 파라미터를 받아오고 Topic컴포넌트로 전달이 되는데, 그 전달 된 값을 useParams를 이용해 받아올 수 있고 동적으로 동작하는 페이지를 만들 수 있다.


*전체코드

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter, Route, Switch, Link, NavLink, useParams } from 'react-router-dom';

function Home() {
  return (
    <div>
      <h2>Home</h2>
      Home...
    </div>
  )
}

let contents = [
  { id: 1, title: 'HTML', description: 'HTML is...' },
  { id: 2, title: 'JS', description: 'JS is...' },
  { id: 3, title: 'React', description: 'React is...' }
]

function Topic() {
  let params = useParams();
  let selected_topic = { //기본값 설정 
    title: 'Sorry',
    description: 'Not Found'
  } 
  contents.forEach(content=>{
  if(content.id===Number(params.id)){
    selected_topic = content
    }
  })
  return (
    <div>
      <h3>{selected_topic.title}</h3>
      {selected_topic.description}
    </div>
  )


}

function Topics() {
  return (
    <div>
      <h2>Topics</h2>
      <ul>
        {contents.map(content =>
          <li><NavLink to={`/topics/${content.id}`} >{content.title}</NavLink></li>
        )}
      </ul>

      <Route path='/topics/:id'>
        <Topic></Topic>
      </Route>
    </div>
  )
}

function Contact() {
  return (
    <div>
      <h2>Contact</h2>
      Contact...
    </div>
  )
}


function App() {
  return (
    <div>
      <h1>React Router DOM</h1>
      <ul>
        <li><NavLink exact to='/'>Home</NavLink></li>
        <li><NavLink to='/topics'>Topics</NavLink></li>
        <li><NavLink to='/contact'>Contact</NavLink></li>
      </ul>
      <Switch>
        <Route exact path='/'><Home></Home></Route>
        <Route path='/topics'><Topics></Topics></Route>
        <Route path='/contact'><Contact></Contact></Route>
        <Route path='/'>Not found</Route>
      </Switch>
    </div>
  )
}


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

출처

생활코딩 | React Router강의

profile
아름다운 상상을 실현하는 개발자입니다🌈🤍

0개의 댓글