[말로 풀어쓴 React] 리액트 라우터로 SPA 개발하기

DongGu·2021년 1월 22일
0

목차

  • SPA란?
  • 프로젝트 준비 및 기본적인 사용법
  • Route 하나에 여러 개의 path 설정하기
  • URL 파라미터와 쿼리
  • 서브 라우트
  • 리액트 라우터 부가 기능

1. SPA란?

1) SPA는 Single Page Application(싱글 페이지 애플리케이션)의 약어다. 기존의 웹사이트들은 페이지를 이동할 때마다 새로운 html을 보여줬다. 서버로부터 리소스를 전달받아 해석해야 가능하다.

페이스북에서 스크롤을 내리거나 특정 버튼을 누를 때마다 서버에게 리소스를 요청해 화면을 보여준다고 생각하면, 사소한 클릭 하나에 얼마나 많은 데이터가 오갈지 생각이 될 것이다.이런 불필요한 낭비, 과정을 줄이기 위해 개발된 것이 SPA이다.

리액트 같은 라이브러리, 프레임워크를 사용하면 뷰 렌더링을 사용자의 브라우저가 담당하고, 우선 애플리케이션을 브라우저에 불러와서 실행시킨 후에 사용자와 인터렉션이 발생하면 필요한 부분만 자바스크립트를 사용해 업데이트한다.

2) 다른 주소에 다른 화면을 보여주는 것을 라우팅이라고 한다. 'www.naver.com'에 접속하면 네이버가, 'www.daum.net'에 접속하면 다음이 뜨는 것이라고 생각하면 된다.

3) SPA는 앱의 규모가 커지면 JS 파일이 너무 커진다는 단점이 있다. 사용자가 무엇을 쓸지 모르기 때문에, 개별적인 기능을 가진 자바스크립트 파일을 전부 불러와야 된다. 코드스플리팅을 사용하면 라우트별로 파일들을 나누어서 트래픽과 로딩 속도를 개선할 수 있다.

또 자바스크립트를 실행하지 않는 일반 크롤러에서는 페이지의 정보를 제대로 수집하지 못한다는 단점이 있다. 포털사이트들은 각 웹페이지들의 정보를 크롤링해서, 사용자들이 검색할 때 그에 맞는 정보를 보여준다. 크롤링이 제대로 되지 않으면 게시글이 타인에게 노출되지 않을 수도 있다.게다가 자바스크립트 파일이 실행될 때 까지 빈 사이트가 보일 수도 있다. 이런 문제점들은 서버 사이드 렌더링을 통해 개서할 수 있다.

Route

  • 프로젝트에 라우터 적용
    import {BrowserRouter} from 'react-rotuer-dom'
    BrowerRouter 컴포넌트는 HTML5의 History API를 사용하여 페이지를 새로고침하지 않고도 주소를 변경하고, 현재 주소에 관련된 정보를 props로 쉽게 조호하거나 사용할 수 있도록 해준다.
  • Route 컴포넌트로 특정 주소에 컴포넌트 연결
    Route컴포넌트를 사용하면 사용자의 현재 경로(url)에 따라 다른 컴포넌트(웹페이지 내용)을 보여줄 수 있다.
    <Route path="주소규칙" component={보여줄 컴포넌트} />
    위의 경우에 Route컴포넌트를 어떻게 사용했을까?
    props에 전달된 것은 About이라는 컴포넌트라고 가정했을 때,
    <Route path="/about" component{About} />일 것이다. /about이라는 주소에 About이라는 컴포넌트를 전달해줬다. 물론 Route 컴포넌트를 사용하기 위해서 import {Route} from 'react-route-dom을 해줘야 한다.

  • Route 하나에 여러 개의 path 설정하기!
    <Route path={["/about", "/info"]} component={About} />처럼 path에 props를 배열로 설정해주면, 어떤 것으로 접속하든 About 컴포넌트가 렌더링된다.

  • Link 컴포넌트를 사용하여 다른 주소로 이동하기
    <Link to="주소">내용</Link>로 다른 주소로 이동할 수 있다. html의 a 태그와 기능이 유사하다. 하지만 a 태그는 페이지를 새로고침하며 불러오고, Link는 페이지 새로고침이 되지 않는다는 차이가 있다.

    위의 사진처럼 '홈' -> 'localhost:3000', '소개' -> 'localhost:3000/about'으로 가려면 어떻게 해야 할까.
    <Link to="/about">소개</Link>를 해주면, '소개'라는 텍스트에 링크 컴포넌트로 '/about'에 연결된다.

URL 파라미터와 쿼리

  • URL 파라미터와 쿼리
    페이지 주소를 정할 때, 가끔 유동적인 값으로 해야할 때도 있다. 파라미터와 쿼리로 나뉜다. 파라미터는 블로그 주소에 들어갈 ID, 쿼리는 검색조건에 많이 쓰인다.
    - 파라미터 예시: blog.naver.com/ehdrn463
    - 쿼리 예시: tab_jum&query
    1) URL 파라미터
    /profile/velopert와 같은 형식으로 뒷부분에 유동적인 username 값을 넣어 줄 때 해당 값을 props로 받아와서 조회하는 방법이다.
    URL 파라미터를 사용할 때는 라우트로 사용되는 컴포넌트에서 받아오는 match라는 객체 안의 params 값을 참조한다. match 객체 안에는 현재 컴포넌트가 어떤 경로 규칙에 의해 보이는지에 대한 정보가 있다.

    match... 누구냐
    A match object contains information about how a <Route path> matched the URL. match objects contain the following properties.

    props가 부모컴포넌트 -> 자식컴포넌트로 변수를 넘길 때 사용되는 것처럼, match는 route -> component로 변수를 넘길 때 사용된다. params, IsExact, path, url이라는 properties를 갖는다. params는 object로 key/value값을 가진다. (https://reactrouter.com/web/api/match)
// Profile.js
import React from "react";

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

const Profile = ({ match }) => {
  console.log(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;
// 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";

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>
      <Route path="/" component={Home} exact={true} />
      <Route path={["/about", "/info"]} component={About} />
      <Route path="/profile/:username" component={Profile} />
    </div>
  );
};

export default App;

여기서 의문이 생겼다. match.parms에 어떻게 username이 저장되어 있는지 궁금했다. Profiles.js 내의 data를 건드리지도 않았는데 말이다. Route에도 :username이라고 전달했을 뿐인데, velopert, gildong 등의 정보가 어떻게 저장되었을까.
이유는 Link에 담긴 주소 정보 때문이었다. Link의 주소 정보에 따라 match.params.username이 바꼈다.
<Link to="/profile/gildong">gildong 프로필</Link>
<Link to="/profile/gildong2">gildong 프로필</Link>로 바꿔봤다. parmas내의 username이 gildong이 아니라 gildong2가 되었다.


2) URL 쿼리
쿼리는 location 객체(window의 객체)에 들어 있는 search값에서 조회 가능하다. location 객체는 라우트로 사용된 컴포넌트에게 props로 전달되며, 웹애플리케이션의 현재 주소에 대한 정보를 지니고 있다.

search 값에서 특정 값을 읽어오기 위해서는 이 문자열을 객체 형태로 변환해줘야 한다. 쿼리 문자열을 객체로 변환할 때는 qs라는 라이브러리를 쓴다.
쿼리를 사용할 때는 쿼리 문자열을 객체로 파싱하는 과정에서 결과값은 언제나 문자열이다. 숫자를 받아와야할 땐 parseInt를, 논리자료형 값을 사용해야 하는 경우는 "ture" 문자열이랑 일치하는지 비교해야 한다.

// 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;

서브 라우트

서브 라우트는 라우트 내부에 또 라우트를 정의하는 것을 의미한다. 기존의 라우트 컴포넌트에 새로운 라우트 컴포넌트를 추가하기만 하면 된다.
Profiles.js의 Route컴포넌트에서 component 대신 render이라는 props를 넣었다.

컴포넌트 자체를 전달하는 것이 아니라, 보여주고 싶은 JSX를 넣어줬다. 따로 컴포넌트를 만들기엔 간단한 일이거나, 컴포넌트에 props를 별도로 넣어주고 싶을 때 사용하면 된다.

// 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(value)</Link>
        </li>
        <li>
          <Link to="/profiles/guildong">guildong</Link>
        </li>
      </ul>

      <Route
        path="/profiles"
        exact
        render={() => <div>사용자를 선택해주세요.</div>}
      />
      <Route path="/profiles/:username" component={Profile} />
    </div>
  );
};

export default Profiles;
// 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="/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;

리액트 라우터 부가기능

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

//HistorySample.js
import React, { Component } from "react";

class HistorySampe 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 HistorySampe;
// 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 HistorySampe from "./HistorySampe";

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>
      <Route path="/" component={Home} exact={true} />
      <Route path={["/about", "/info"]} component={About} />
      <Route path="/profiles" component={Profiles} />
      <Route path="/history" component={HistorySampe} />
    </div>
  );
};

export default App;

2) withRouter
withRouter 함수는 Hoc(Higher-order Component)이다. 라우트로 사용된 컴포넌트(라우트 컴포넌트, <Route path="/profiles" component={Profiles} /> 같은 것) 가 아니어도 match, location, history 객체에 접근할 수 있게 만들어준다. match, location, history 객체들은 Route 컴포넌트에서만 접근할 수 있다. withRouter를 쓰면 일반적인 컴포넌트에서도 match, location, history를 사용 가능하다.

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);

자바스크립트에서 객체를 JSON.stringify로 문자열로 만들어줬던 것처럼 적고, 두 번째 및 세 번째 파라미터인 null, 2는 빈공간을 2칸 들여쓰기한다는 뜻이다. 아래 사진은 두칸 들여쓰기 된 모습이다.

그런데 match.parmas의 value가 비어있다. withRoute를 사용하면 현재 자신을 보여주고 있는 라우트 컴포넌트(현재 Profiles)를 기준으로 match가 전달된다. Profiles를 위한 라우트를 설정할 때는 path="/profiles"라고만 입력해서 username파라미터가 없어서, 읽어오지 못한다. username은 Profile 컴포넌트에 있다. 그래서 Profiles에서 Profile로 옮겨오면 match.params가 제대로 보인다.

3) Swtich
Switch 컴포넌트는 여러 Route를 감싸서 그 중 일치하는 단 하나의 라우트만을 렌더링시켜준다. 모든 규칙과 일치하지 않으면 Not Found 페이지를 띄워줄 수도 있다. 없는 주소를 입력하면 가장 마지막 Route가 렌더링된다.

<Switch>
  <Route path="/" component={Home} exact={true} />
  <Route path={["/about", "/info"]} component={About} />
  <Route path="/profiles" component={Profiles} />
  <Route path="/history" component={HistorySampe} />
  <Route
    render={({ location }) => (
      <div>
        <h2>이 페이지는 존재하지 않습니다</h2>
        <p>{location.pathname}</p>
      </div>
    )}
  />
</Switch>

4) NavLink
NavLink는 Link와 비슷하다. 현재 경로와 Link에서 사용하는 경로가 일치하지 않는 경우, 특정 스타일 혹은 CSS 클래스를 적용할 수 있는 컴포넌트다.
NavLink에서 링크가 활성화되었을 때의 스타일을 적용할 때는 activeStyle값을, CSS 클래스를 적용할 때는 activeClassName 값을 props로 넣어주면 된다.
gildong주소 일 때, gildong이 음영쳐진 것을 볼 수 있다.

  <ul>
    <li>
      <NavLink activeStyle={activeStyle} to="/profiles/velopert">
        velopert
      </NavLink>
    </li>
    <li>
      <NavLink activeStyle={activeStyle} to="/profiles/gildong">
        gildong
      </NavLink>
    </li>
  </ul>

개선할 점: /about 페이지에 들어가면 당장 필요하지 않은 Profile 컴포넌트까지 불러온다. 라우트에 따라 필요한 컴포넌트만 불러오고, 다른 컴포넌트는 다른 페이지를 방문하는 등의 필요한 시점에서만 불러오면 더 효율적일 것이다. 이를 해결해주는 기술이 코드 스플리팅이다. 끝

'리액트를 다루는 기술(김민준, 길벗 출판사)'를 참고하여 작성했습니다. 제가 읽으면서 이해가 부족했던 부분을 채우기 위해 작성한 글입니다. 더 자세한 내용은 도서를 참고하시기를 권장드립니다.

profile
코딩하는 신방과생

0개의 댓글