📖 리액트/리액트를 다루는 기술

13장 리액트 라우터로 SPA 개발하기

놀러와요 버그의 숲 2022. 2. 8. 22:04
728x90
반응형

이 글은 『리액트를 다루는 기술』(개정판/ 김민준 저 / 길벗 출판사)이라는 책을 참고하여 썼습니다.

 

학습범위: p324 ~p352

 

 

 

학습목표

 

1. 전통적인 웹페이지의 어떤 문제를 SPA가 해결해주었는지 설명할 수 있다.

 

2. Route path와 comonent 를 이용하여 url별로 페이지를 다르게 보여줄 수 있다.

 

3. Link 컴포넌트를 사용할 수 있다.

 

 

 

13.1 SPA란?

SPA(Single Page Application)

: 한 개의 페이지로 이루어진 애플리케이션 

 

 

 

전통적인 웹 페이지 방식

 

기존에는 사용자가 다른 페이지로 이동할 때마다 새로운 html을 받아오고,

페이지를 로딩할 때마다 서버에서 리소스를 전달받아 해석한 뒤 화면에 보여주었다.

이렇게 사용자에게 보이는 화면은 서버 측에서 준비를 했었다.

 사전에 html 파일을 만들어서 제공하거나, 데이터에 따라 유동적인 html을 생성해 주는 템플릿 엔진을 사용하기도 했다.

 

 

문제점

 

요즘은 웹에서 제공되는 정보가 정말 많기 때문에 새로운 화면을 보여 주어야 할 때마다 서버 측에서 모든 뷰를 준비한다면 성능상의 문제가 발생할 수 있다. 예를 들면 트래픽이 너무 많이 나올 수도 있고, 사용자가 몰려 서버에 높은 부하가 쉽게 걸릴 수도 있다.

사용자와의 인터랙션이 자주 발생하는 모던 웹 애플리케이션에는 더더욱 적당하지 않다.

애플리케이션 내에서 화면 전환이 일어날 때마다 html을 계속 서버에 새로 요청하면 사용자의 인터페이스에서 사용하고 있던 상태를 유지하는 것도 번거롭고, 바뀌지 않는 부분까지 새로 불러와서 보여 주어야 하기 때문에 불필요한 로딩이 있어서 비효율적이다.

 

 

 

해결책

 

그래서 리액트 같은 라이브러리 혹은 프레임워크를 사용하여 뷰 렌더링을 사용자의 브라우저가 담당하도록 하고,

우선 애플리케이션을 브라우저에 불러와서 실행시킨 후에 사용자와의 인터랙션이 발생하면 필요한 부분만 자바스크립트를 사용하여

업데이트해 줍니다.

만약 새로운 데이터가 필요하다면 서버 API를 호출하여 필요한 데이터만 새로 불러와 애플리케이션에서 사용할 수도 있다.

 

 

 

싱글 페이지라고 해서 화면이 한 종류일까? 꼭 그렇지만은 않다.

예를 들어 블로그를 개발한다면 홈, 포스트 목록, 포스트, 글쓰기 등의 화면이 있을 것이다.

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

 

다른 주소에 다른 화면을 보여 주는 것을 라우팅이라고 한다. 리액트 라이브러리 자체에 이 기능이 내장되어 있지는 않는다.

그 대신 브라우저의 API를 직접 사용하여 이를 관리하거나, 라이브러리를 사용하여 이 작업을 더욱 쉽게 구현할 수 있다.

 

리액트 라우팅 라이브러리는 리액트 라우터(react-router), 리치 라우터(reach-router), Next.js 등 여러 가지가 있다.

이번 포스팅에서는  그중 역사가 가장 길고 사용 빈도가 가장 높은 리액트 라우터를 사용한다.

리액트 라우터는 클라이언트 사이드에서 이루어지는 라우팅을 아주 간단하게 구현할 수 있도록 해준다.

더 나아가서 나중에 서버 사이드 렌더링을 할 때도 라우팅을 도와주는 컴포넌트들을 제공해 준다.

 

 

SPA 단점 

 

SPA의 단점은 앱의 규모가 커지면 자바스크립트 파일이 너무 커진다는 것이다.

페이지 로딩 시 사용자가 실제로 방문하지 않을 수도 있는 페이지의 스크립트도 불러오기 때문이다.

 

리액트 라우터처럼 브라우저에서 자바스크립트를 사용하여 라우팅을 관리하는 것은 자바스크립트를 실행하지 않는 일반 크롤러에서는 페이지의 정보를 제대로 수집해 가지 못한다는 잠재적인 단점이 따른다.

그렇기 때문에 구글, 네이버, 다음 같은 검색 엔진의 검색 결과에 페이지가 잘 나타나지 않을 수도 있다.

구글 검색 엔진에서 사용하는 크롤러의 경우 자바스크립트를 실행해 주는 기능이 탑재되어 있기는 하지만, 크롤링하는 모든 페이지에서 자바스크립트를 실행하고 있지는 않는다.

 

또한, 자바스크립트가 실행될 때까지 페이지가 비어 있기 때문에 자바스크립트 파일이 로딩되어 실행되는 짧은 시간 동안 흰 페이지가 나타날 수 있다는 단점도 있다.

 

이러한 문제점들은 다행히 나중에 배우게 될 서버 사이드 렌더링(server-side rendering)을 통해 모두 해결할 수 있다.

 

 

 

 

 

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

 

 

리액트 라우터를 설치할 때는 yarn을 사용하여 react-router-dom이라는 라이브러리를 설치해야한다.

 

$ yarn add react-router-dom

 

 

 

13.2.2 프로젝트에 라우터 적용

 

src/index.js 파일에서

BrowserRouter라는 컴포넌트를 사용하여 감싸면된다.

 

import React from "react";
import { BrowserRouter } from "react-router-dom";
import ReactDOM from "react-dom";
import App from "./App";

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

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

 

 

 

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

 

 

Route라는 컴포넌트를 사용하여 사용자의 현재 경로에 따라 다른 컴포넌트를 보여 주겠다.

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

 

<Route path=“주소규칙“ component={보여 줄 컴포넌트} />
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;

 

하지만! /about 과 / 이 겹치기에 두 컴포넌트가 동시에 렌더링되는 현상 발생! 🐛

 

 

=> 이를 수정하려면 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;

 

 

 

 

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

 

 

Link 컴포넌트는 클릭하면 다른 주소로 이동시켜 주는 컴포넌트이다.

일반 웹 애플리케이션에서는 a 태그를 사용하여 페이지를 전환한다.

리액트 라우터를 사용할 때는 이 태그를 직접 사용하면 안 된다.

이 태그는 페이지를 전환하는 과정에서 페이지를 새로 불러오기 때문에 애플리케이션이 들고 있던 상태들을 모두 날려 버리게 된다. 🤯

렌더링된 컴포넌트들도 모두 사라지고 다시 처음부터 렌더링하게 되는 것이다.

 

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

Link 컴포넌트 자체는 a 태그로 이루어져 있지만, 페이지 전환을 방지하는 기능이 내장되어 있다.

 

사용방법

<Link to=“주소“>내용</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;

 

 

 

 

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

 

 

Route를 두 번 사용하는 대신, path props배열로 설정해 주면 여러 경로에서 같은 컴포넌트를 보여 줄 수 있다.

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', '/info']} component={About} />
    </div>
  );
};
 
export default App;

 

 

 

13.4 URL 파라미터와 쿼리

 

 

페이지 주소를 전달할때 유동적인 값을 전달해야 할 때도 있다.

이는 파라미터와 쿼리로 나눌 수 있다.

 

 파라미터 예시: /profiles/junghee

 쿼리 예시: /about?details=true

 

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

 

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

 

(뭔가 파라미터는 내 프로필 정보 같은 것 사용할 때 쓰고, 쿼리는 정보 검색 할 때 정도로 간단히 생각했다.)

 

 

 

 

 

13.4.1 URL 파라미터

 

/profile/junghee와 같은 형식으로 뒷부분에 유동적인 username 값을 넣어줄 때 해당 값을 props로 받아 와서

조회하는 방법을 알아보자.

 

const data = {
  kakao: {
    name: "카카오톡",
    description: "한국 sns 대표 주자",
    adress: "www.kakao.com",
  },
  naver: {
    name: "네이버",
    description: "한국 웹사이트 대표 주자",
    adress: "www.naver.com",
  },
};

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

 

URL 파라미터를 사용할 때는 라우트로 사용되는 컴포넌트에서 받아 오는 match라는 객체 안의 params 값을 참조한다.

match 객체 안에는 현재 컴포넌트가 어떤 경로 규칙에 의해 보이는지에 대한 정보가 들어있다.

 

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

function App() {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
        <li>
          <Link to="/profile/kakao">카카오 프로필</Link>
        </li>
        <li>
          <Link to="/profile/naver">네이버 프로필</Link>
        </li>
      </ul>
      <Route path="/" component={Home} exact={true} />
      <Route path={["/about", "/info"]} component={About} />
      <Route path="/profile/:companyName" component={Profile} />
    </div>
  );
}

export default App;

 

APP 컴포넌트에서 Profile 컴포넌트를 위한 라우트를 정의해보자.

path 규칙에 /profile/:companyName이라고 넣어주자.

이렇게 설정하면 match.params.companyName 값을 통해 현재 companyName 값을 조회할 수 있다.

 

 

 

13.4.2 URL 쿼리

 

 

이번에는 About 페이지에서 쿼리를 받아오겠다.

 

쿼리는 location 객체에 들어있는 search 값에서 조회할 수 있다.

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

 

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

 

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

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

URL 쿼리는 ?detail=true&another=1과 같이 문자열에 여러 가지 값을 설정해 줄 수 있다.

search 값에서 특정 값을 읽어 오기 위해서는 이 문자열을 객체 형태로 변환해 주어야 한다.

 

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

 

$ yarn add qs

 

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 주소로 직접 들어간 후 접속해 보자.

 

 

 

 

13.5 서브라우트

 

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

const Profiles = () => {
  return (
    <div>
      <h3>사용자 목록</h3>
      <ul>
        <li>
          <Link to="/profiles/kakao">카카오</Link>
        </li>
        <li>
          <Link to="/profiles/naver">네이버</Link>
        </li>
      </ul>
      <Route
        path="/profiles"
        exact
        render={() => <div>사용자를 선택해주세요</div>}
      />
      <Route path="/profiles/:companyName" component={Profile} />
    </div>
  );
};

export default Profiles;

 

이 코드에서 첫 번째 Route 컴포넌트에는 component 대신 render라는 props를 넣어 준다.

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

지금처럼 따로 컴포넌트를 만들기 애매한 상황에 사용해도 되고, 컴포넌트에 props를 별도로 넣어 주고 싶을 때도 사용할 수 있다.

 

JSX에서 props를 설정할 때 값을 생략하면 자동으로 true로 설정된다. 예를 들어 현재 Profile 컴포넌트의 첫 번째 Route에서 exact={true} 대신 그냥 exact라고만 적어 주었는데, 이는 exact={true}와 같은 의미다.

 

컴포넌트를 다 만들었다면 기존의 App 컴포넌트에 있던 프로필 링크를 지우고, Profiles 컴포넌트를 /profiles 경로에 연결시켜 주자.

그리고 해당 경로로 이동하는 링크도 추가하자.

 

 

 

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

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

export default App;

useParams에 path= ": id" 객체의 키 값이 된다. 

 

 

13.6 리액트 라우터 부가 기능

 

 

13.6.1 history

 

history 객체는 라우트로 사용된 컴포넌트에 match, location과 함께 전달되는 props 중 하나로, 이 객체를 통해 컴포넌트 내에 구현하는 메서드에서 라우터 API를 호출할 수 있다.

 

ex) 특정 버튼을 눌렀을 때 뒤로 가거나, 로그인 후 화면을 전환하거나, 다른 페이지로 이탈하는 것을 방지해야 할 때 history를 활용한다.

 

 

 

 

13.6.2 withRouter

 

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

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

 

 

 

 

 

13.6.3 Switch

 

Switch 컴포넌트는 여러 Route를 감싸서 그중 일치하는 단 하나의 라우트만을 렌더링시켜 준다.

Switch를 사용하면 모든 규칙과 일치하지 않을 때 보여 줄 Not Found 페이지도 구현할 수 있다.

 

※ react-router-dom 6버전부터 사용불가.

 

import React from ‘react‘;
import { Route, Link, Switch } from ‘react-router-dom‘;
import About from ‘./About‘;
import Home from ‘./Home‘;
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=“/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;

 

 

 

13.6.4 NavLink

 

 

NavLink는 Link와 비슷하다.

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

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

 

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;

 

정리

 

1. 전통적인 웹페이지의 어떤 문제를 SPA가 해결해주었는지 설명할 수 있다. + SPA의 단점 

 

=> 요즘 같이 정보가 많은 웹에는 화면전환이 일어날때 마다 리렌더링 되거나, 모든 페이지를 서버에서 일일이 제공하기 힘들기 때문에 SPA가 필요

 

 

2. Route path와 comonent 를 이용하여 url별로 페이지를 다르게 보여줄 수 있다.

 

=> 경로에 따라 다른 컴포넌트를 보여주는 기능

 

방법: <Route path="페이지 규칙" component={보여줄 컴포넌트 } / > 

 

 

3. Link 컴포넌트를 사용할 수 있다.

 

=> 존재이유: a태그를 이용하면 페이지 전체가 리렌더링되기에 Link 컴포넌트 이용 

 

방법: <Link to="페이지 주소"> 내용 </Link>