ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React :: 13. 리액트 라우터로 SPA 개발하기
    Dev/프론트엔드 2021. 7. 22. 10:54
    이 글은 김민준(벨로퍼트)님의 - 리액트를 다루는 기술 도서를 참고하여 학습하는 글 입니다.

    그동안 책의 내용을 그대로 적었는데 올바른 학습방법이 아닌것같다.

    이제부터는 먼저 읽어보고 이해한 내용을 토대로 작성하도록 해보겠다.

    13.1 SPA란?

    SPA는 Single Page Application의 약어이다. 말 그대로 한 개의 페이지로 이루어진 애플리케이션이라는 의미이다. 기존의 웹 페이지는 여러 페이지로 구성이 되어 있었다.

    기존에는 사용자가 다른 페이지로 이동할 때 서버에서 html을 받아 오고, 페이지를 로딩할 때 서버에서 리소스를 전달받아 브라우저가 해석한 뒤 렌더링이 되었다. 즉 사용자에게 보이는 화면은 서버 측에서 준비하였다. 사전에 html 파일을 만들어서 제공하거나, 데이터에 따라서 유동적인 html을 생성해주는 템플릿 엔진을 사용하기도 하였다.

    요즘은 웹에서 제공되는 정보가 무수히 많아 새로운 화면을 사용자에게 보여줘야 할 때마다 서버 측에서 모든 뷰를 준비한다면 성능상 문제가 발생할 수 있다. 예를 들어 서버의 트래픽이 너무 많이 나오거나 사용자가 몰려 서버에 높은 부하를 가하게 될 수도 있다. 속도와 트래픽 측면에서는 캐싱과 압축을 통해 서비스를 제공하면 어느 정도 최적화될 수 있지지만, 사용자와의 인터렉션이 자주 발생하는 모던 웹 애플리케이션에서는 적당하지 않을 수 있다.

    애플리케이션 내에서 화면 전환이 일어날 때마다 html을 계속 서버에 요청하게 되면 사용자의 인터페이스에서 상태 유지하는 것도 번거로우며 바뀌지 않는 영역까지 불러와서 보여주어야 하기 때문에 비효율적인 면이 많다.

    전통적인 웹 페이지, SPA (출처 : https://www.excellentwebworld.com/what-is-a-single-page-application/)

    그래서 리액트 같은 라이브러리 혹은 프레임워크를 사용하여 뷰 렌더링을 사용자의 브라우저(클라이언트 자원)가 담당하도록 하고, 먼저 애플리케이션을 브라우저에 불러와서 실행시킨 후 사용자와의 인터렉션이 발생할 경우 필요한 부분만 자바스크립트를 이용하여 업데이트를 해준다. 만약 새로운 데이터가 필요하다면 서버에 존재하는 API를 호출하여 필요한 데이터만 새로 받아와 애플리케이션에서 사용할 수도 있다.

     

    싱글페이지라고 해서 화면이 꼭 한 종류인것은 아니다. 예를 들어 블로그를 개발한다면, 홈, 포스트 목록, 포스트, 글쓰기 등의 화면이 있을 것이고 SPA의 경우 서버에서 사용자에게 제공하는 페이지는 한 종류이지만, 해당 페이지는 로딩된 자바스크립트와 현재 사용자 브라우저의 주소에 따라 다양한 화면을 보여 줄 수 있다.

    이 때 다른 주소에 다른 화면을 보여주는 것을 라우팅이라고 한다. 리액트 라이브러이 자체에 이 기능이 내장되어 있지는 않지만 ,브라우저의 API를 직접 사용하여 이를 관리하거나, 라이브러리를 사용하여 이 작업을 쉽게 구현할 수 있다.

     

    리액트 라우팅 라이브러리는 리액트 라우터(react-router), 리치 라우터(reach-router), Next,js 등 여러가지가 있다. 책에서는 가장 역사가 길고 사용 빈도가 높은 리액트 라우터를 사용한다.

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

    13.1.1 SPA의 단점

    그렇다면 SPA 단점을 알아보자.

    SPA의 단점은 애플리케이션의 규모가 커지면 자바스크립트 파일이 너무 커지는것이다. 페이지 로딩 시 사용자가 실제로 보지 않을수도 있는 페이지의 스크립트까지 불러오기 때문이다. 하지만 걱정하지 않아도 된다. 나중에 배울 코드 스플리팅(code splitting)을 사용하면 라우트별로 파일들을 나누고 트래픽과 로딩 속도를 개선 할 수 있다.

     

    리액트 라우터처럼 브라우저에서 자바스크립트를 사용하여 라우팅을 관리하는 것은 자바스크립트를 실행하지 않은 일반 크롤러에서는 페이지의 정보를 제대로 수집해 가지 못한다는 잠재적인 단점이 있다. 그렇기 때문에 구글, 네이버, 다음 같은 검색 엔진의 검색 결과에 페이지가 잘 노출되지 않을 수도 있다. 구글 검색 엔진에서 사용하는 크롤러의 경우 자바스크립트를 실행 해주는 기능이 탑재되어 있긴 하지만, 크롤링하는 모든 페이지에서 자바스크립트를 실행하고 있지는 않는다(2019년 기준). 또한, 자바스크립트가 실행될 때까지 페이지가 비어 있기에 자바스크립트 파일이 로딩되어 실행되는 짧은 시간 동안 흰 페이지가 나타날 수 있다는 단점도 있다. 이러한 문제점들은 나중에 배우게 될 서버 사이드 렌더링을 통해 모두 해결할 수있다.

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

    이제 SPA가 무엇인지 알았다. 리액터 라우터의 역할도 알았으니 본격적으로 리액트 라우터를 사용해보자.

    이번 실습은 다음 흐름대로 진행된다.

    프로젝트 생성 및 리액트 라우터 적용 - 페이지 만들기 - Route 컴포넌트로 특정 주소에 컴포넌트 연결 -

    라우트 이동하기 - URL 파라미터와 쿼리 이해하기 - 서브 라우트 - 부가 기능 알아보기

    13.2.1 프로젝트 생성 및 라이브러리 설치

    우선 리액트 라우터를 적용해 볼 리액트 프로젝트를 새로 생성해보자

    $ yarn create react-app router-tutorial

    그리고 해당 프로젝트 디렉토리로 이동하여 리랙트 라우터 라이브러리를 설치해야 한다. 리액트 라우터를 설치할때는 yarn을 사용하여 react-router-dom이라는 라이브러리를 설치하면 된다.

    $ cd router-tutorital
    $ yarn add react-router-dom

    13.2.2 프로젝트에 라우터 적용

    프로젝트에 리액트 라우터를 적용할 때는 src/index.js 파일에서 react-router-dom에 내장되어 있는 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";
    
    ReactDOM.render(
      <BrowserRouter>
        <App />
      </BrowserRouter>,
      document.getElementById("root")
    );

    13.2.3 페이지 만들기

    이제 라우트로 사용할 페이지 컴포넌트를 만들 차례이다. 사용자가 웹 사이트에 접속했을때 처음으로 보여줄 Home 컴포넌트와 웹 사이트를 소개하는 About 컴포넌트를 만들어 보자. src 디렉터리에 다음 파일을 만들자.

    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;

    이제 페이지로 사용할 모든 컴포넌트가 완성되었다.

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

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

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

    사용 방식은 다음과 같다

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

    App.js를 열어서 기존 코드를 모두 제거하고, Route 컴포넌트를 사용하여 방금 만든 Home 컴포넌트 혹은 About 컴포넌트를 보여 주도록 설정해 보자.

    App.js
    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;

    여기까지 코드를 작성한 뒤 터미널에 yarn start를 입력하여 개발 서버를 시작해 보세요. 첫 화면에 다음과 같이 Home 컴포넌트가 나타날 것이다.

    13-4 Home

    다음으로 주소창에 localhost:300/about 경로를 입력하여 들어가 보자

    13-5 페이지에 컴포넌트 두개

    /about 경로로 들어가면 About 컴포넌트만 나오기를 기대했지만, 예상과 다르게 두 컴포넌트가 모두 나타난다.

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

    App.js
    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;

    다시 브라우저를 확인해보면 About컴포넌트가 하나만 잘 나타난다.

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

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

     

    Link 컴포넌트를 사용하여 페이지를 전환하면, 페이지를 새로 불러오지 않고 애플리케이션은 그대로 유지한 상태에서 HTML5 History API를 사용하여 페이지의 주소만 변경해 준다. Link컴포넌트 자체는 a 태그로 이루어져 있지만, 페이지 전환을 방지하는 기능이 내장되어 있다.

    Link 컴포넌트는 다음과 같이 사용한다.

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

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

    App.js
    import React from 'react';
    import { Link, Route } 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-7 Link 컴포넌트 사용하기

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

    Route 하나에 여러 개의 path를 지정하는 것은 최신 버전의 리액트 라우터 v5부터 적용된 기능이다.

    이전 버전에서는 여러 개의 path에 같은 컴포넌트를 보여 주고 싶다면 다음과 같이 해야 한다.

    App.js
    const App = () => {
      return (
        <div>
        ( ... )
          <hr/>
          <Route path="/" component={Home} exact={true}/>
          <Route path="/About" component={About}/>
          <Route path="/info" component={About}/>
        </div>
      );
    };
    
    export default App;

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

    App.js
    const App = () => {
      return (
        <div>
         ( ... )
          <hr/>
          <Route path="/" component={Home} exact={true}/>
          <Route path={['/about','/info']} component={About}/>
        </div>
      );
    };
    
    export default App;

    코드를 저장하고, 주소창에 http://localhost:3000/info를 입력하여 들어가 소개 페이지가 잘 나타나는지 확인해 보자.

    13.4 URL 파라미터와 쿼리

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

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

    유동적인 값을 사용해야 하는 상황에서 파라미터를 써야할지 쿼리를 써야 할지 정할 때, 무조건 따라야 하는 규칙은 없다. 다만 일반적으로 파라미터는 특정 아이디 혹은 이름을 사용하여 조회할때 사용하고. 쿼리는 우리가 어떤 키워드를 검색하거나 페이지에 필요한 옵션을 전달할때 사용한다.

    13.4.1 URL 파라미터

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

    Profile이라는 컴포넌트를 다음과 같이 만들어보자.

    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 컴포넌트를 위한 라우트를 정의해 보자. 이번에 사용할 path 규칙에는 /profile/:username 이라고 넣어주면 된다. 이렇게 설정하게 되면 match.params.username 값을 통해 현재 username 값을 조회할 수 있다.

    라우트를 정의하고 나서 상단에 각 프로필 페이지로 이동하는 링크도 추가해보자.

    App.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;

    그림 13-8 URL 파라미터 사용하기

    프로필 페이지가 잘 나타나는지 확인해보자.

    13.4.2 URL 쿼리

    이번에는 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을 사용하여 해당 라이브러리를 설치해보자.

    $ yarn add qs

    그러면 이제 About 컴포넌트에서 loaction.search 값에 있는detail true인지 아닌지에 따라 추가 정보를 보여 주도록 만들어보자. About 컴포넌트를 다음과 같이 수정해보자.

    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;

    쿼리를 사용할 때는 쿼리 문자열을 객체로 파싱하는 과정에서 결과 같은 언제나 문자열이다. ?value=1 혹은 ?value=true와 같이 숫자나 논리 자료형(boolean)을 사용한다고 해서 해당 값이 우리가 원하는 형태로 변환되는 것이 아니라. "1", "true"와 같이 문자열 형태로 받아진다.

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

    코드를 전부 작성했다면, 브라우저에서 http://localhost:3000/about?detail=true 주소로 직접 들어간 후 접속해보자.

    URL 쿼리 사용하기

    하단에 "detail 값을 true로 설정하셨군요!"라는 문구가 잘보인다.

     

    13.5 서브 라우트

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

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

    Profiles.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;

    이 코드에서 첫 번째 Route 컴포넌트에는 component 대신 render라는 props를 넣어 주었다. 컴포넌트 자체를 전달하는 것이 아니라, 보여 주고 싶은 JSX를 넣어 줄 수 있다. 지금처럼 따로 컴포넌트를 만들기 애매한 상황에 사용해도 되고, 컴포넌트에 props를 별도로 넣어 주고 싶을떄도 사용할 수 있다.

     

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

    컴포넌트를 다 만들었다면 기존의 App 컴포넌트에 있던 프로필 링크를 지우고, Profiles 컴포넌트를 /profiles 경로에 연결시켜줘야한다. 그리고 해당 경로로 이동하는 링크도 추가해보자.

    App.js
    import React from 'react';
    import { Link, Route } 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;

    13-10 서브 라우트 만들기

    서브라우트가 잘 작동된다.

    13.6 리액트 라우터 부가 기능

    13.6.1 history

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

    이 객체를 사용하는 예제 페이지를 한번 작성해 볼까요? HistorySample이라는 컴포넌트를 다음과 같이 만들어 보자.

    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에서 /history 경로에 해당 컴포넌트가 보이도록 설정해 보세요.

    App.js
    import React from 'react';
    import { Link, Route } 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;

    이제 http://localhost:3000/history 페이지에 들어가 하단에 있는 버튼들이 잘 작동하는지 확인해보세요.

    링크를 눌러서 현재 페이지를 이탈하려고 할 때 "정말 떠나실 건가요?" 라는 브라우저 메시지 창이 뜰 것입니다.

     

    13.6.2 WithRouter

    withRouter 함수는 HoC(Higher-order Component)입니다. 라우트로 사용된 컴포넌트가 아니어도 match, location, history 객체를 접근할 수 있게 해 줍니다.

    WithRouterSample이라는 컴포넌트를 만들어서 withRouter 함수를 사용해 보겠습니다.

    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를 사용할 때는 컴포넌트를 내보내 줄 때 함수로 감싸 줍니다. JSON.stringify의 두 번째 파라미터와 세 번째 파라미터를 위와 같이 null,2로 설정해 주면 JSON에 들여쓰기가 적용된 상태로 문자열이 만들어집니다.

    다 만들었다면 이 컴포넌트를 Profiles 컴포넌트에 렌더링해 보세요.

    Profiles.js
    import React from "react";
    import { Link, Route } from "react-router-dom";
    import Profile from "./Profile"
    import WithRouterSample from "./WithRouterSample";
    
    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}/>
    
          <WithRouterSample/>
        </div>
      );
    };
    
    export default Profiles;

    이제 http://localhost:3000/profiles 페이지에 들어가 보세요. 하단에 location과 match 객체의 정보가 나타나는지 확인해보자.

     

    withRouter 사용하기

    Textarea 크기 조정
    브라우저에서 Textarea의 우측 하단 모서리를 드래그하면 Textarea의 크기를 조정할 수 있습니다.

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

     

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

    Profile.js
    import React from "react";
    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;

    Profile에서 withRouter 사용

    이번에는 params.username을 제대로 보여 주고 있습니다.

    13.6.3 Switch

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

    App.js
    import React from 'react';
    import { Link, Route, 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;

    이젠 존재하지 않는 페이지인 http://localhost:3000/nowhere에 들어가 보세요.

    Switch 컴포넌트 사용해 보기

    위와 같이 나오면 정상적으로 동작하는 것입니다.

    13.6.4 NavLink

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

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

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

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

    코드를 다 작성했다면 각 프로필 페이지를 열어 보세요.

    NavLink

    사용자 목록에 있는 링크를 클릭했을 때 색상이 제대로 바뀌나요?

    13.7 정리

    이 장에서는 리액트 라우터를 사용하여 주소 경로에 따라 다양한 페이지를 보여 주는 방법을 알아 보았습니다.

    큰 규모의 프로젝트를 진행하다 보면 한 가지 문제가 발생합니다. 바로 웹 브라우저에서 사용할 컴포넌트, 상태 관리를 하는 로직, 그 외 여러 기능을 구현하는 함수들이 점점 쌓이면서 최종 결과물인 자바스크립트 파일의 크기가 매우 커진다는 점입니다.

    예를 들어 방금 만든 프로젝트는 사용자가 /about 페이지에 들어왔을 때 지금 당장 필요하지 않은 Profile 컴포넌트까지 불러옵니다. 라우트에 따라 필요한 컴포넌트만 불러오고, 다른 컴포넌트는 다른 페이지를 방문하는 등의 필요한 시점에 불러오면 더 효율적이지 않을까요? 이를 해결해주는 기술이 바로 코드 스플리팅입니다. 이에 대해서는 19장에서 다루겠습니다.

    이어지는 14장에서는 지금까지 배웠던 지식들을 활용하여 최신 뉴스 목록을 보여 주는 프로젝트를 만들어 보겠습니다.

    댓글

Designed by Tistory.