상세 컨텐츠

본문 제목

[리액트를 다루는 기술]13장 리액트 라우터로 SPA 개발하기

21-22/21-22 리액트 마스터

by Kimpeep 2021. 12. 27. 13:00

본문

728x90

1. SPA란?

SPA(Single Page Application)
한 개의 페이지로 이루어진 애플리케이션이라는 의미

1) 전통적인 웹 페이지와 SPA 비교

- 전통적인 웹 페이지

  • 사용자가 다른 페이지로 이동할 때마다 새로운 html을 받아온다.
  • 페이지를 로딩할 때마다 서버에서 리소르를 전달받아 해석한 뒤 화면에 보여준다.
  • 사용자에게 보이는 화면을 서버 측에서 준비한다.
  • 사전에 html파일을 만들어 제공하거나, 데이터에 따라 유동적인 html을 생성해 주는 템플릿 엔진을 사용한다.

- SPA

먼저, SPA를 사용하는 이유에 대해 알아보자!

1. 웹에서 제공되는 정보가 많아 새로운 화면을 보여 줘야 할 때마다 서버 측에서 모든 뷰를 준비한다면 성능상의 문제가 발생할 수 있다. (트래픽 ↑, 서버 과부하)
2. html을 서버에 요청할 때, 인터페이스에서 사용하고 있던 상태를 유지하는 것도 번거롭고, 바뀌지 않는 부분까지 새로 불러와 보여 주어야 하기 때문에 불필요한 로딩이 있어 비효율적이다.

  • 리액트 같은 라이브러리 혹은 프레임워크를 사용하여 뷰 렌더링을 브라우저가 담당하도록 한다.
  • 애플리케이션을 브라우저에 불러와서 실행시킨 후, 사용자와의 인터랙션이 발생하면 필요한 부분만 자바스크립트를 사용하여 업데이트 한다.
  • 새로운 데이터가 필요하면 서버 API를 호출하여 필요한 데이터만 새로 불러와 애플리케이션에서 사용한다.

2) SPA에서 다양한 화면 보여주기

SPA는 서버에서 사용자에게 제공하는 페이지는 한 종류이지만,
해당 페이지에서 로딩된 자바스크립트와 함께, 사용자와 브라우저의 주소 상태에 따라
다양한 화면을 보여 줄 수 있다.
→ 이것이 바로 라우팅!

- 리액트 라우팅 라이브러리 종류

  • 리액트 라우터(react-router)
  • 리치 라우터(reach-router)
  • Next.js

- 리액트 라우터 특징

  • 클라이언트 사이드에서 이루어지는 라우팅을 아주 간단하게 구현할 수 있도록 한다.
  • 서버 사이드 렌더링을 할 때, 라우팅을 도와주는 컴포넌트들을 제공해준다.

3) SPA 단점

  1. 앱의 규모가 커지면 자바스크립트 파일이 너무 커진다.
    • (해결법) 코드 스플리팅을 사용해 라우트 별로 파일들을 나누어서 트랙픽과 로딩 속도를 개선할 수 있다.
  2. 브라우저에서 자바스크립트를 사용하여 라우팅을 관리하면, 자바스크립트를 실행하지 않는 일반 크롤러에서는 페이지의 정보를 제대로 수집해 가지 못한다는 잠재적인 단점이 있다.
  3. 자바스크립트가 실행될 때까지 페이지가 비어있기 때문에 자바스크립트 파일이 로딩되어 실행되는 짧은 시간 동안 흰 페이지가 나타날 수 있다.
    • (해결법) 서버 사이드 렌더링

2. 프로젝트 준비 및 기본적인 사용법

1) 프로젝트 생성 및 리액트 라우터 적용

- 프로젝트 생성하기

$ yarn create react-app router-tutorial

- 라우터 설치하기

$ yarn add react-router-dom@5

2) 프로젝트에 라우터 적용

<적용법>

src/index.js 파일에서 react-router-dom에 내장되어 있는 BrowserRouter라는 컴포넌트를 사용하여 감싼다.

  • BrowserRouter 컴포넌트는 웹 애플리케이션 HTML5의 History API를 사용하여 페이지를 새로고침하지 않고도 주소를 변경한다.
  • 또한, 현재 주소에 관련된 정보를 props로 쉽게 조회하거나 사용할 수 있게 해준다.

src/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")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

3) 페이지 만들기

라우트로 사용할 페이지 컴포넌트 만들기

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;

 

4) Route 컴포넌트로 특정 주소에 컴포넌트 연결

<Route 컴포넌트 사용방식>
<Route path=”주소 규칙” component={보여 줄 컴포넌트} />

App.js

import React from "react";
import { Route, Routes } 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;

<결과보기>

  • 위와 같이 /about으로 이동했지만 About 컴포넌트만 보이는 것이 아닌 Home 컴포넌트도 같이 보인다.
    • (해결법) /about 경로가 /규칙에도 일치해졌기 때문인데, exact라는 props를 true로 설정하면 컴포넌트 하나만 나오게 할 수 있다.
    • <Route path="/" component={Home} exact={true} />

5) Link 컴포넌트를 사용하여 다른 주소로 이동하기

- Link 컴포넌트

  • 클릭하면 다른 주소로 이동시켜 주는 컴포넌트
  • 페이지를 새로불러오지 않고 그대로 유지한 상태에서 HTML5 History API를 사용하여 페이지의 주소만 변경해 준다.

- Link 컴포넌트 사용법

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

- 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} />
      <Route path="/about" component={About} />
    </div>
  );
};

export default App;

3. Route 하나에 여러 개의 path 설정하기

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} />
      <Route path={["/about", "/info"]} component={About} />
    </div>
  );
};

export default App;
  • /info로 접속해도 About 컴포넌트를 볼 수 있다.

4. URL 파라미터와 쿼리

페이지 주소를 정의할 때 유동적인 값을 전달해야 할 경우 파라미터나 쿼리를 사용한다.

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

일반적으로 파라미터는 특정 아이디 혹은 이름을 사용하여 조회할 때 사용하고

쿼리는 어떤 키워드를 검색하거나 페이지에 필요한 옵션을 전달할 때 사용한다.

1) URL 파라미터

/profile/velopert와 같은 형식으로

뒷부분에 유동적인 username 값을 넣어 줄 때

해당 값을 props로 받아 와 조회하는 방법을 알아보자

 

Profile.js

import React from "react";

const data = {
  velopert: {
    name: "김민준",
    description: "리액트를 좋아하는 개발자",
  },
  gildong: {
    name: "홍길동",
    description: "고전 소설 홍길동전의 주인공",
  },
};

const Profile = ({ match }) => {
  const { username } = match.params;
  const profile = data[username];
  if (!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 컴포넌트를 위한 라우터 정의하기

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/velopert">velopert 프로필</Link>
        </li>
        <li>
          <Link to="/profile/gildong">gildong 프로필</Link>
        </li>
      </ul>
      <hr />
      <Route path="/" component={Home} />
      <Route path={["/about", "/info"]} component={About} />
      <Route path="/profile/:username" component={Profile} />
    </div>
  );
};

export default App;

2) URL 쿼리

About 페이지에서 쿼리를 받아오기

쿼리는 location 객체에 들어 있는 search 값에서 조회 가능하며

location 객체는 라우트로 사용된 컴포넌트에게 props로 전달된다.

 

location의 형태

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

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

 

URL 쿼리의 특징

  • URL 쿼리를 읽을 때는 search 값을 확인해야 한다.
  • search 값은 문자열 형태로 되어있다.
  • URL 쿼리는 문자열에 여러 가지 값을 설정해 줄 수 있다.
  • search 값에서 특정 값을 읽어 오기 위해서는 문자열을 객체 형태로 변환해 주어야 한다.
    • 이를 위해 qs라이브러리 설치하기
       $yarn add qs​
About 컴포넌트에서 location.search값에 있는 detail이 true인지 아닌지에 따라 추가 정보를 보여 주도록 만들어보자

About.js

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;
  • 쿼리를 사용할 때 쿼리 문자열을 객체로 파싱하는 과정에서 결과값은 언제나 문자열이기 때문에 비교할 때에도 “true”와 같이 정확하게 문자열을 비교하도록 한다.

5. 서브 라우트

라우트 내부에 또 다른 라우트를 정의하는 것

APP 컴포넌트에서 두 종류의 프로필 링크를 보여 줬는데,

이를 잘라내서 프로필 링크를 보여 주는 Profiles라는 라우트 컴포넌트를 따로 만들고,

그 안에서 Profile 컴포넌트를 서브 라우트로 사용하도록 한다.

 

Profile.js

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/velopert">velopert</Link>
        </li>
        <li>
          <Link to="/profiles/gildong">gildong</Link>
        </li>
      </ul>
 
      <Route
        path="/profiles"
        exact
        render={() => <div>사용자를 선택해 주세요.</div>}
      />
      <Route path="/profiles/:username" component={Profile} />
    </div>
  );
};
 
export default Profiles;

첫번째 라우터의 render props

→ 컴포넌트 자체를 전달하는 것이 아닌 보여주고 싶은 JSX를 넣어 줄 수 있다.

 

App.js

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

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

export default App;

6. 리액트 라우터 부가 기능

1) history

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

HistorySample.js

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;

App.js

import React from "react";
import { Route, Link } from "react-router-dom";
import About from "./About";
import Home from "./Home";
import Profile from "./Profile";
import Profiles from "./Profiles";
import HistorySample from "./HistorySample";
const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
        <li>
          <Link to="/profile/velopert">velopert 프로필</Link>
        </li>
        <li>
          <Link to="/profile/gildong">gildong 프로필</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="/profile/:username" component={Profile} />
      <Route path="/profiles" component={Profiles} />
      <Route path="/history" component={HistorySample} />
    </div>
  );
};

export default App;

2) withRouter

withRouter함수는 HoC(Higher-order Component)이다.

라우트로 사용된 컴포넌트가 아니어도 match, location, history 객체를 접근할 수 있게 해 준다.

 

WithRouterSample.js

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


export default withRouter(WithRouterSample);

  • withRouter를 사용하면 현재 자신을 보여 주고 있는 라우트 컴포넌트(현재 Profile)를 기준으로 match가 전달된다.
  • Profiles를 위한 라우트를 설정할 때 path=”/profiles”라고만 입력했기 때문에 username 파라미터를 읽어 오지 못하는 상태이다.
  • WithRouterSample 컴포넌트를 Profiles에서 지우고 Profile 컴포넌트에 넣으면 match쪽에 URL파라미터가 제대로 보이게 된다.

Profile.js

import React from "react";
import { withRouter } from "react-router-dom";
import WithRouterSample from "./WithRouterSample";

const data = {
  velopert: {
    name: "김민준",
    description: "리액트를 좋아하는 개발자",
  },
  gildong: {
    name: "홍길동",
    description: "고전 소설 홍길동전의 주인공",
  },
};

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

export default Profile;

3) Switch

  • 여러 Route를 감싸서 그 중 일치하는 단 하나의 라우트만을 렌더링 시켜 준다.
  • Switch를 사용해 모든 규칙과 일치하지 않을 때 보여 줄 Not Found 페이지도 구현할 수 있다.
import React from "react";
import { Route, Link, Switch } from "react-router-dom";
import About from "./About";
import Home from "./Home";
import Profile from "./Profile";
import Profiles from "./Profiles";
import HistorySample from "./HistorySample";
const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
        <li>
          <Link to="/profile/velopert">velopert 프로필</Link>
        </li>
        <li>
          <Link to="/profile/gildong">gildong 프로필</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="/profile/:username" component={Profile} />
        <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;

4) NavLink

  • NavLink는 Link와 비슷하다.
  • 현재 경로와 Link에서 사용하는 경로가 일치하는 경우 특정 스타일 혹은 CSS클래스를 적용할 수 있는 컴포넌트이다.
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/velopert" active>
            velopert
          </NavLink>
        </li>
        <li>
          <NavLink activeStyle={activeStyle} to="/profiles/gildong">
            gildong
          </NavLink>
        </li>
      </ul>
      (…)
    </div>
  );
};

export default Profiles;

퀴즈!

Q1) 다른 주소에 다른 화면을 보여주는 것을 뭐라고 할까요

더보기

라우팅

Q2) URL에 유동적인 값을 전달할 때 사용하는 2가지를 적어주세요

더보기

파라미터, 쿼리

Q3) 라우트의 부가기능 중 라우트로 사용된 컴포넌트가 아니어도 match, location, history 객체를 접근할 수 있게 해주는 함수는 무엇일까요

더보기

withRouter

728x90

관련글 더보기