공부 기록

[react] 리액트 라우터

by 너나나

SPA

SPA는 Single Page Application의 약어, 말 그대로 한 개의 페이지로 이루어진 애플리케이션이다!! 리액트 같은 친구들은 새로운 화면을 보여줘야 할 때 마다 서버측에서 모든 뷰를 준비하는게 아니라 뷰 렌더링을 사용자의 브라우저가 담당하게 하고 애플리케이션을 브라우저에 불러와 실행시킨 후에 필요한 부분만 업데이트 한다!!!

싱글 페이지라고 해서 화면이 한 종류가 아니라 서버에서 사용자에게 제공하는 페이지가 한 종류이고, 해당 페이지에서 로딩된 자바스크립트와 현재 사용자 브라우저의 주소 상태에 따라 다양한 화면을 보여줄 수 있다!!!

 

다른 주소에 다른 화면을 보여주는 것을 라우팅이라고 한다!! 리액트 라우팅 라이브러리중 리액트 라우터를 이용해보자!!

리액트 라우터

프로젝트를 생성하고 해당 프로젝트 디렉터리로 이동하여

yarn add react-router-dom

라이브러리를 설치하자!!! yarn말고 npm 사용해도 됨!!!

프로젝트에 리액트 라우터를 적용할 때는 src/index.js파일에서 react-router-dom에 내장되어있는 BrowserRouter이라는 컴포넌트를 사용하여 감싸면 된다!! 이 컴포넌트는 웹 애플리케이션에 HTML5의 history API를 사용하여 페이지를 새로고침하지 않고도 주소를 변경하고, 현재 주소에 관련된 정보를 props로 쉽게 조회하거나 사용할 수 있도록 해준다!!

// index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom'
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

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

이제 라우트로 사용할 페이지 컴포넌트를 만들 차례이다!!

사용자가 웹 사이트에 들어왔을 때 맨 처음 보여 줄 Home 컴포넌트와 웹 사이트를 소개하는 About컴포넌트를 만들자!!

// Home.js

import React from 'react';

const Home = () => {
    return (
        <div>
            <h1>홈</h1>
            <p>홈!! 가장 먼저 보여지는 페이지</p>
        </div>
    );
};

export default Home;
// About.js

import React from 'react';

const About = () => {
    return (
        <div>
            <h1>소개</h1>
            <p>라액트 라우터 기초를 실습 해보는 예제 페이지</p>
        </div>
    )
}

export default About;

이 두개를 페이지로 사용할꺼다!!

 

이제 Route라는 컴포넌트를 사용하여 사용자의 현재 경로에 따라 다른 컴포넌트를 보여줄것이다!!

Route 컴포넌트를 사용하면 어떤 규칙을 가진 경로에 어떤 컴포넌트를  보여 줄지 정의할 수 있다.

<Route path="주소규칙" component={보여 줄 컴포넌트} />

이렇게 사용하면 된다!!

App.js의 기존 코드를 모두 지우고 Route 컴포넌트를 사용해서 Home과 About 컴포넌트를 보여주도록 설정해보자

import React from 'react';
import { Route } from 'react-router-dom';
import About from './About';
import Home from './Home';

const App = () => {
  return (
    <div>
      <Route path="/" component={Home} />
      <Route path="/about" component={About} />
    </div>
  );
};

export default App;

이제 프로젝트를 실행해보면 첫 화면에

우리가 의도한대로 홈 화면이 뜨고

localhost:3000뒤에 /about 경로를 입력해서 들어가보면

허걱 About 컴포넌트만 나오는게 아니라 홈까지 나온다!!

/about 경로가 / 규칙에도 일치하기 때문에 발생한 현상이다. 이를 수정하려면 Home을 위한 Route 컴포넌트를 사용할 때 exact라는 props를 true로 설정하면 된다.

import React from 'react';
import { Route } from 'react-router-dom';
import About from './About';
import Home from './Home';

const App = () => {
  return (
    <div>
      <Route path="/" component={Home} exact={true} />
      <Route path="/about" component={About} />
    </div>
  );
};

export default App;

다시 확인해보면 컴포넌트 하나만 잘 나타나는것을 확인할 수 있다!!

 

Link 컴포넌트

link 컴포넌트는 클릭하면 다른 주소로 이동시켜 주는 컴포넌트이다. 일반 웹 애플리케이션에서는 a 태그를 사용하여 메세지를 전환하는데 리액트 라우터를 사용할 때는 이 태그를 직접 사용하면 안된다!! a 태그는 페이지를 전환하는 과정에서 페이지를 새로 불러오기 때문에 애플리케이션이 들고 있던 상태를 모두 날려버리게 돼서 렌더링된 컴포넌트들도 모두 사라지고 처음부터 렌더링하게 된다!!

 

link 컴포넌트를 사용하여 페이지를 전환하면, 페이지를 새로 불러오지 않고 애플리케이션은 그대로 유지한 상태에서 history api를 사용하여 페이지의 주소만 변경해준다.

<Link to="주소">내용</Link>

App 컴포넌트에서 "/", "/about" 경로로 이동하는 Link 컴포넌트를 만들어보자

import React from 'react';
import { Route, Link } from 'react-router-dom';
import About from './About';
import Home from './Home';

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
      </ul>
      <hr />
      <Route path="/" component={Home} exact={true} />
      <Route path="/about" component={About} />
    </div>
  );
};

export default App;

잘 작동하는 것을 볼 수 있다!!

 

여러개의 path에 같은 컴포넌트를 보여주고 싶으면 path props를 배열로 설정해주면 된다.

      <Route path={['/about', '/info']} component={About} />

이렇게 해주면 localhost:3000/info를 들어가도 소개페이지를 볼 수 있다!!!

URL 파라미터와 쿼리

페이지 주소를 정의할 때 가끔은 유동적인 값을 전달해야 할 때도 있다!! 얘네는 파라미터와 쿼리로 나눌 수 있다

  • 파라미터 예시: /profile/youme
  • 쿼리 예시: /about?details=true

유동적인 값을 사용해야 하는 상황에서 파라미터를 써야 할지 쿼리를 써야 할지 정할때, 무조건 따라야 하는 규칙은 없다!!

일반적으로는 파라미터는 특정 아이디 혹은 이름을 사용하여 조회할때 사용하고, 쿼리는 어떤 키워드를 검색하거나 페이지에 필요한 옵션을 전달할 때 사용한다!!

 

Profile 페이지에서 파라미터를 사용해보자!! /props/youme와 같은 형식으로 뒷부분에 유동적인 username 값을 넣어 줄 때 해당 값을 props로 받아와서 조회하는 방법을 알아보자아아

 

Profile 컴포넌트를 만들었다!!

import React from 'react';

const data = {
    youme: {
        name: '김너나',
        description: '리액트가 싫어요 살려주세요'
    },
    hwihyeol: {
        name: '반휘혈',
        description: '세계 서열 0위'
    }
};

const Profile = ({match}) => {
    const {username} = match.params;
    const profile = data[username];
    if (!profile) { // profile이 존재하지 않으면
        return <div>존재하지 않는 사용자입니다.</div>
    }
    return (
        <div>
            <h3>
                {username}({profile.name})
            </h3>
            <p>{profile.description}</p>
        </div>
    );
};

export default Profile;

 

URL 파라미터를 사용할 때는 라우트로 사용되는 컴포넌트에서 받아 오는 match라는 객체 안의 params 값을 참조한다!! match 객체 안에는 현재 컴포넌트가 어떤 경로 규칙에 의해 보이는지에 대한 정보가 들어있다.

 

이제 App 컴포넌트에서 Profile 컴포넌트를 위한 라우트를 정의해보자!! 이번에 사용할 path 규칙에는 /profile/:usename이라고 넣어주면 된다!! 이렇게 설정하면 match.params.username값을 통해 현재 username 값을 조회할 수 있다.

import React from 'react';
import { Route, Link } from 'react-router-dom';
import About from './About';
import Home from './Home';
import Profile from './Profile';

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
        <li>
          <Link to="/profile/youme">김너나 프로필</Link>
        </li>
        <li>
          <Link to="/profile/hwihyeol">반휘혈 프로필</Link>
        </li>
      </ul>
      <hr />
      <Route path="/" component={Home} exact={true} />
      <Route path={['/about', '/info']} component={About} />
      <Route path="/profile/:username" component={Profile} />
    </div>
  );
};

export default App;

프로필이 잘 나타난다!!

이번에는 About 페이지에서 쿼리를 받아오자!! 쿼리는 location 객체에 들어 있는 search 값에서 조회할 수 있다.

location 객체는 라우트로 사용된 컴포넌트에게 props로 전달되며, 웹 애플리케이션의 현재 주소에 대한 정보를 지니고 있다.

location의 형태는 다음과 같다.

{
    "pathname": "/about",
    "search": "?detail=true",
    "hash": ""
}

위 location 객체는 http://localhost:3000/about?detail=true 주소로 들어갔을 때의 값이다!!

 

URL 쿼리를 읽을 때는 위 객체가 지닌 값 중에서 search 값을 확인해야 한다. 이 값은 문자열 형태로 되어 있다.

URL 쿼리는 ?detail=true&another=1과 같이 문자열에 여러가지 값을 설정해 줄 수 있다. search 값에서 특정 값을 읽어 오기 위해서는 이 문자열을 객체 형태로 변환해주어야한다!!

 

쿼리 문자열을 객체로 변환할 때는 qs라는 라이브러리를 사용한다. yarn(npm)을 사용하여 해당 라이브러리를 설치하자.

npm add qs

이제 About 컴포넌트에서 location.search 값에 있는 detail이 true 인지 아닌지에 따라 추가 정보를 보여주도록 만들자!!

import React from 'react';
import qs from 'qs';

const About = ({location}) => {
    const query = qs.parse(location.search, {
        ignoreQueryPrefix: true // 문자열 맨 앞의 ?를 생략한다.
    });
    const showDetail = query.detail === 'true'; // 쿼리의 파싱 결과 값은 문자열
    return (
        <div>
            <h1>소개</h1>
            <p>라액트 라우터 기초를 실습 해보는 예제 페이지</p>
            {showDetail && <p>detail 값을 true로 설정했구만!!!</p>}
        </div>
    )
}

export default About;

쿼리를 사용할 때는 쿼리 문자열을 객체로 파싱하는 과정에서 결과 값은 언제나 문자열이라는 점에 주의해야한다.

?value=1 혹은 ?value=true 같이 숫자나 boolean을 사용한다고 해서 해당 값이 우리가 원하는 형태로 변환되는 것이 아니라, "1", "true" 와 같이 문자열 형태로 받아진다.

그렇기 때문에 숫자를 받아 와야 하면 parseInt 함수를 통해 꼭 숫자로 변환해 주고, 지금처럼 논리 자료형 값을 사용해야 하는 경우에는 정확히 "true"문자열과 일치하는지 비교해줘야한다.

 

http://localhost:3000/about?detail=true 주소로 직접 들어가보면

하단 문구가 잘 보이는것을 확인할 수 있다!!

서브 라우트

서브 라우트는 라우트 내부에 또 라우트를 정의하는 것을 의미한다. 이 작업은 그렇게 복잡하지 않다. 그냥 라우트로 사용되고 있는 컴포넌트 내부에 Route 컴포넌트를 또 사용하면 된다!!

 

기존의 App 컴포넌트에서는 두 종류의 프로필 링크를 따로 보여줬는데 얘를 잘라내서 프로필 링크를 보여주는 Profiles 라는 라우트 컴포넌트를 따로 만들고, 그 안에서 Profile 컴포넌트를 서브 라우트로 사용하도록 코드를 작성해보자!!!

Profiles라는 컴포넌트를 만들었다.

import React from 'react';
import { Link, Route } from 'react-router-dom';
import Profile from './Profile';

const Profiles = () => {
    return (
        <div>
            <h3>사용자 목록:</h3>
            <ul>
                <li>
                    <Link to="/profiles/youme">김너나 프로필</Link>
                </li>
                <li>
                    <Link to="/profiles/hwihyeol">반휘혈 프로필</Link>
                </li>
            </ul>

            <Route 
                path="/profiles"
                exact // exact={true} 와 같은 의미
                render={() => <div>사용자를 선택해 주세요.</div>} // 컴포넌트 자체를 전달하는것이 아니라, 보여 주고 싶은 JSX를 넣음
                />
            <Route path="/profiles/:username" component={Profile} />
        </div>
    
    )
}

export default Profiles;

기존 App 컴포넌트에 있던 프로필 링크를 지우고, Profiles 컴포넌트를 /profiles 경로에 연결시켜 주자!! 링크도 추가!!

import React from 'react';
import { Route, Link } from 'react-router-dom';
import About from './About';
import Home from './Home';
import Profiles from './Profiles';

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
        <li>
          <Link to="/profiles">프로필</Link>
        </li>
      </ul>
      <hr />
      <Route path="/" component={Home} exact={true} />
      <Route path={['/about', '/info']} component={About} />
      <Route path="/profiles" component={Profiles} />
    </div>
  );
};

export default App;

잘 나온다!!!

리액트 라우터 부가 기능

1. history

history 객체는 라우트로 사용된 컴포넌트에 match, location과 함께 전달되는 props 중 하나로, 이 객체를 통해 컴포넌트 내에 구현하는 메서드에서 라우터 API를 호출할 수 있다. 예를 들어 특정 버튼을 눌렀을 때 뒤로 가거나, 로그인 후 화면을 전환하고나, 다른 페이지로 이탈하는 것을 방지해야 할 때 history를 사용한다!!

 

HistorySample이라는 컴포넌트를 만들어보자!!

import React, { Component } from 'react';

class HistorySample extends Component {
    // 뒤로 가기
    handleGoback = () => {
        this.props.history.goBack();
    }
    // 홈으로 이동
    handleGoHome = () => {
        this.props.history.push('/');
    }
    componentDidMount() {
        // 페이지에 변화가 생기려고 할 때마다 정말 나갈 것인지를 질문함
        this.unblock = this.props.history.block('ㄹㅇ 떠나실 건가요ㅠㅠ');
    }
    componentWillUnmount() {
        //컴포넌트가 언마운트되면 질문을 멈춤
        if (this.unblock) {
            this.unblock();
        }
    }

    render() {
        return (
            <div>
                <button onClick = {this.handleGoback}>뒤로</button>
                <button onClick = {this.handleGoHome}>홈으로</button>
            </div>
        )
    }
}
export default HistorySample
import React from 'react';
import { Route, Link } from 'react-router-dom';
import About from './About';
import HistorySample from './HistorySample';
import Home from './Home';
import Profiles from './Profiles';

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
        <li>
          <Link to="/profiles">프로필</Link>
        </li>
        <li>
          <Link to="/history">History 예제</Link>
        </li>
      </ul>
      <hr />
      <Route path="/" component={Home} exact={true} />
      <Route path={['/about', '/info']} component={About} />
      <Route path="/profiles" component={Profiles} />
      <Route path="/history" component={HistorySample} />
    </div>
  );
};

export default App;

뒤로 누르면 그 전 페이지로 잘 이동하고 홈으로 누르면 홈으로 잘 이동한다!!!

2. withRouter

라우트로 사용된 컴포넌트가 아니어도 match, location, history 객체에 접근할 수 있게 해주는 친구다!!

WithRouterSample 이라는 컴포넌트를 만들어서 withRouter을 사용해보자!!!

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)}
                row={7}
                readOnly={true}
            />
            <h4>match</h4>
            <textarea
                value={JSON.stringify(match, null, 2)}
                row={7}
                readOnly={true}
            />
            <button onClick={() => history.push('/')}>홈으로</button>
        </div>
    )
}

export default withRouter(WithRouterSample)

withRouter을 사용할 때는 컴포넌트를 내보낼때 함수로 감싸준다.

이 컴포넌트를 Profiles에 렌더링해보면

location과 match 객체의 정보를 볼 수 있다.

그런데 여기서 match 객체를 보면 params가 비어있다. withRouter을 사용하면 현재 자신을 보여주고 있는 라우트 컴포넌트(현재 Profiles)를 기준으로 match가 전달된다. Profiles를 위한 라우트를 설정할 때는 path="profiles"라고만 입력했으므로 username 파라미터를 읽어오지 못하는 상태이다.

WithRouterSample 컴포넌트를 Profiles에서 지우고 Profile 컴포넌트에 넣으면 match쪽에 url 파라미터가 제대로 보일것이다!!

3. Switch

Switch 컴포넌트는 여러 Route를 감싸서 그중 일치하는 단 하나의 라우트만을 렌더링시켜준다!! Switch를 사용하면 모든 규칙과 일치하지 않을 때 보여 줄 Not Found 페이지도 구현할 수 있다!!

import React from 'react';
import { Route, Link, Switch } from 'react-router-dom';
import About from './About';
import HistorySample from './HistorySample';
import Home from './Home';
import Profiles from './Profiles';

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
        <li>
          <Link to="/profiles">프로필</Link>
        </li>
        <li>
          <Link to="/history">History 예제</Link>
        </li>
      </ul>
      <hr />
      <Switch>
        <Route path="/" component={Home} exact={true} />
        <Route path={['/about', '/info']} component={About} />
        <Route path="/profiles" component={Profiles} />
        <Route path="/history" component={HistorySample} />
        <Route
          // path를 따로 정의하지 않으면 모든 상황에 렌더링됨
          render={({location}) => (
            <div>
              <h2>페이지 낫 파운드!!!!!</h2>
              <p>{location.pathname}</p>
            </div>
          )} />
      </Switch>
    </div>
  );
};

export default App;

이제 존재하지 않는 페이지인 nowhere에 들어가보자!!

설정한 페이지 낫 파운드가 잘 뜬다!!

4. NavLink

NavLink는 Link와 비슷한데 현재 경로와 Link에서 사용하는 경로가 일치하는 경우 특정 스타일 혹은 CSS 클래스를 적용할 수 있는 컴포넌트이다.

 

NavLink에서 링크가 활성화되었을 때의 스타일을 적용할 때는 activeStyle 값을, CSS 클래스를 적용할 때는 activeClassName 값을 props로 넣어주면 된다.

Profiles에서 사용하고 있는 컴포넌트에서 Link 대신 NavLink를 사용하게 하고, 현재 선택되어 있는 경우 검정색 배경에 흰색 글씨로 스타일을 보여 주게끔 코드를 수정해보자!!

import React from 'react';
import { NavLink, Route } from 'react-router-dom';
import Profile from './Profile';

const Profiles = () => {
    const activeStyle = {
        background: 'black',
        color: 'white'
    };
    return (
        <div>
            <h3>사용자 목록:</h3>
            <ul>
                <li>
                    <NavLink activeStyle={activeStyle} to="/profiles/youme">
                        김너나 프로필
                    </NavLink>
                </li>
                <li>
                    <NavLink activeStyle={activeStyle} to="/profiles/hwihyeol">
                        반휘혈 프로필
                    </NavLink>
                </li>
            </ul>

            <Route 
                path="/profiles"
                exact // exact={true} 와 같은 의미
                render={() => <div>사용자를 선택해 주세요.</div>} // 컴포넌트 자체를 전달하는것이 아니라, 보여 주고 싶은 JSX를 넣음
                />
            <Route path="/profiles/:username" component={Profile} />
        </div>
    
    )
}

export default Profiles;

김너나 프로필 눌렀을때
반휘혈 프로필 눌렀을때

잘 되는걸 볼 수 있다!!!!!!!!!!!!!!!


참고 자료

「리액트를 다루는 기술 - 김민준」

'study > react (2021 여름방학)' 카테고리의 다른 글

[react] 코드 스플리팅  (0) 2021.08.23
[react] 리덕스  (0) 2021.08.15
[react] Hooks  (0) 2021.07.26
[react] 라이프사이클 메서드  (0) 2021.07.24
[react] 컴포넌트 반복  (0) 2021.07.18

블로그의 정보

공부 기록

너나나

활동하기