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

iGhost·2021년 8월 30일
post-thumbnail

SPA란?

Single Page Application의 약어

한개의 페이지로 이루어진 애플리케이션이라는 의미

  • 요즘에는 웹에서 제공되는 정보가 정말 많이 때문에 새로운화면을 보여주어야 할때마다 서버측에서 모든 뷰를 준비한다면 성능상의 문제가 발생할수 있다.
  • 하지만, 리액트는 뷰 랜더링을 사용자의 브라우저가 담당하도록 하고, 우선 애플리케이션을 브라우저에서 불러와서 실행시킨 후에 사용자와의 인터랙션이 발생하면 필요한 부분만 JS를 사용해 업데이트해준다
  • 마찬가지로 새로운 데이터가 필용하다면 서버 API를 호출해 필요한 데이터만 불러오면 되는것이다.

라우팅?

다른 주소에 다른 화면을 보여주는것

  • 리액트 내장 기능은 아니지만, 브라우저의 API를 직접 사용하여 이를 관리하거나, 라이브러리를 사용하여 이 작업을 더욱 쉽게 구현할수 있다.

SPA의 단점

  • 앱의 규모가 커지면 자바스크립트 파일이 너무 커진다

  • 실제로 방문하지 않을 수도 있는 페이지의 스크립트도 불러오기 때문

    ⇒ 코드 스필리팅을 사용하면 라우별로 파일을 나누어서 해결할수있다

  • 리액트 라우터 처럼 브라우저의 API를 이용하게 된다면 ,JS를 사용하지 않는 일반 페이지는 정보를 제대로 수집하기 어렵다는 단점이 있다.

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

    ⇒ 나중에 배우게될 서버 사이드 랜더링을 통해 해결할수있다

프로젝트 준비

리액트 앱 만들고

npm add react-router-dom

1. 프로젝트에 라우터 적용

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

이 컴포넌트는

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

2. home, About 페이지 만들어서 라우팅 해보기

import React from 'react'

function home() {
    return (
        <div>
            <h1></h1>
            <p>, 그페이지느 가장 먼저 보여지는 페이지</p>
        </div>
    )
}

export default home
.....//.....
import React from 'react'

function About() {
    return (
        <div>
            <h1>소개</h1>
            <p> 이 프로젝트는 리액트 라우터 기초를 실습해 보는 예제 프로젝트 입니다</p>
        </div>
    )
}

export default About

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

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

  • Route 컴포넌트를 사용하면 어떤 규칙을 가진 경로에 어떤 컴포넌트를 보여 줄지 정의할수있다.
<Route path = "주소규착" component ={보여줄 컴포넌트}/>

App.js

import './App.css';
import { Route } from 'react-router-dom/cjs/react-router-dom.min';
import home from './home';
import About from './About';

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

export default App;

하지만 About경로로 가면 home도 같이 나온다.?

⇒ /about 경로가 / 규칙에도 일치하기 때문이다

Home을 위한 Route 컴포넌트를 사용할때에는 exact라는 props를 true로 설정해주자

<Route path="/" component={home} exact={true} />

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

  • 원래 일반 웹 애플리케이션에서는 a 태그를 사용하여 페이지를 전환 했는데,

  • 리액트 라우터를 사용할 때는 이 태그를 직접 사용하면 안된다 ⇒ 페이지가 새로고침 되어서, 기존 상태가 사라짐

    ⇒ Link는 페이지는 유지한상태로 페이지 주소만 변경해줌

    ⇒ 페이지 전환을 막는 기능이 기본적으로 들어가 있음

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

App.js

import './App.css';
import { Link, Route } from 'react-router-dom/cjs/react-router-dom.min';
import home from './home';
import About from './About';

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

export default App;

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

예전에는 ?

			<Route path="/" component={home} exact={true} />
      <Route path="/about" component={About} />
      <Route pate="/info" component={About} />

리액트 라우터 v5부터는?

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

URL 파라미터와 쿼리

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

값을 전달해주는 방법은?

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

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

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

URL 파라미터

Profile.js

import React from 'react'

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

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

export default Profile

/// App.js
import './App.css';
import { Link, Route } from 'react-router-dom/cjs/react-router-dom.min';
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/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} />
	// match.params.username값을 통해 현재 username을 조회할수 있게 된다.
    </div>
  );
}

export default App;
  • 뒷부분에 유동적으로 들어올값을 username이라 하고, 해당 파람을 App.js → Profile로 넘겨주면
  • 받아오는 match라를 객체안에 params 값을 참조한다,
  • match 객체 안에는 현재 컴포넌트가 어떤 경로 규칙에 의해 보이는지에 대한 정보가 들어가있다

즉,변수 ⇒ 들어오는 인자를 파라미터를 통해 profile 컴포넌트로 보내주는것

URL 쿼리

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

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

location의 형태

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

= http://locationhost:3000/about?detail=true 주소로 들어갔을때 값

  • url 쿼리를 읽을 때는 위 객체가 지닌 값중에서 search 값을 확인해야한다
  • 이 값은 문자열 형태로 되어있다
  • url 쿼리는 ?detail=true&anther=1과 같이 문자열에 여러가지 값을 설정해 줄 수있다
  • search 값에서 특정 값을 읽어 오기 위해서는 이 문자열을 객체 형태로 변환해 주어야 한다.

⇒ 쿼리를 이용할땐 qs라이브러리를 사용한다

yarn add qs

About.js

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

function About({ location }) { // Route 경로를 통해서 들어오는 location객체, 이중 serceh를 해석
    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
  • About 컴포넌트에서 location.search 값에 잇는 detail이 true인지 아닌지에 따라 추가 정보를 보여줌
  • 쿼리를 사용할때는 쿼리 문자열을 객체로 파싱하는 과정에서 결고 값은 언제나 문자열이라는 점을 주의
  • ?vlaue=1 혹은 ?value-tru와 같이 숫자나 논리형 자료여도 ⇒ 걍 '1' 'true' 이렇게 받는다
  • 그래서 숫자는 꼭 parseInt로 변화, 지금 처럼 논리형이면 정확히 true 문자열이랑 일치하는지 확인

결론적으로 파람이는 쿼리이든 라우터에서 주소(경로)로 보내는주는 값을 각자 match,location 객체를 이용해서 해석해서 값을 사용할수있다.

서브 라우트

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

너무 어렵게 생각하지말고

  • 라우트로 사용되고 있는 컴포넌트(App.js)의 내부에 Route 컴포넌트를 또 사용하면 된다.
import React from 'react'
import { Link, Route } from 'react-router-dom';
import Profile from './Profile';

function Profiles() {
    return (
        <div>
            <h3>사용자 목록:</h3>
            <ul>
                <li>
                    <Link to="/profiles/velopert">veloport</Link>
                </li>
                <li>
                    <Link to="/profiles/gildong">gildonf</Link>
                </li>
            </ul>

            <Route
                path="profiles"
                exact // 자동으로 true가 된다
                render={() => <div>사용자를 선택해주세요</div>} // 보여주고 싶은 jsx를 넣어줄수 있다.
            />
            <Route path="/profiles/:username" component={Profile} />
        </div>
    )
}

export default Profiles

//App.js

기존 App컴포넌트는 두 종류의 프로필 링크를 보여줬는데, 이거는 이를 잘래내서 프로필 링크를 보여주는 프리필 링크를 보여줌

  • profile 컴포넌트가 서브 라우터가 됨
  • 첫번째 Route 컴포넌트에 component 대신 ⇒ reder를 사용 = 컴포넌트 자체가 아니라 보여주고 싶은 JSX도 가능
  • JSX에 props를 생략하면 자동을 true가된다

한 컴포넌트 안에 또 라우트를 해서 컴포넌트 → 컴포넌트가 된다


리액트 라우터 부가기능

history

라우터 API 호출해, 페이지 전환, 뒤로가기 , 페이지 이탈 방지 등에 쓰임

라우트로 사용된 컴포넌트에 match,location과 함꼐 전달되는 props중 하나이다

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
  • 확인 메세지 이후에 홈으로 이동(푸쉬한곳으로)

withRouter

Hoc함수이다, 라우트로 사용된 컴포넌트가 아니어도 match, location,history 객체에 접근하게 함

import React from 'react'
**import { withRouter } from 'react-router-dom'**

function 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()로 감싼다

  • match의 params가 비어있는 이유는 현재 자신을 보여주고 있는 라우트 컴포넌트(지금은 profiles)를 기준으로 match가 전달되기 때문

    ⇒ 지금 profiles 라우트는 path="/profiles" 로 되어 있기 때문에 username 파라 미터를 읽어 오지 못했다(없으니깐)

    switch

    여러 Router를 감싸서 그중 일치하는 단 하나의 라우트만 랜더링한다

    import './App.css';
    import { Link, Route, **Switch** } from 'react-router-dom/cjs/react-router-dom.min';
    import home from './home';
    import About from './About';
    import Profiles from './Profiles';
    import HistorySample from './HistorySample';
    
    function 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>
          **<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를 따로 정의하지 않으면 모든 상황에 렌더링 됨// 암거나 적으면 아무것도 정의하지 않은 Path로
              render={({ location }) => (
                <div>
                  <h2>이 페이지는 존재하지 않습니다:</h2>
                  <p>{location.pathname}</p>
                </div>
              )}
            />
          </Switch>**
        </div>
      );
    }
    
    export default App;
    • 모든 규칙과 일치 하지 않을때 보여줄 Not Found 페이지 구현 가능

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

  • 링크가 활성화 할때의 스타일을 적용할때 ⇒ activeStyle 값을 넣는다
  • CSS 클래스를 적용할 때 ⇒ activeClassName 값을 props로 넣는다
import React from 'react'
import { Link, Route, **NavLink** } from 'react-router-dom';
import Profile from './Profile';
import WithRouterSample from './WithRouterSample';

function Profiles() {
    const activeStyle = {
        background: 'black',
        color: 'white'
    }
    return (
        <div>
            <h3>사용자 목록:</h3>
            <ul>
                <li>
                    **<NavLink activeStyle={activeStyle} to="/profiles/velopert">veloport</NavLink>
                </li>
                <li>
                    <NavLink activeStyle={activeStyle} to="/profiles/gildong">gildonf</NavLink>
                </li>**
            </ul>
            <WithRouterSample />

            <Route
                path="profiles"
                exact // 자동으로 true가 된다
                render={() => <div>사용자를 선택해주세요</div>} // 보여주고 싶은 jsx를 넣어줄수 있다.
            />
            <Route path="/profiles/:username" component={Profile} />
        </div>
    )
}

export default Profiles
profile
인터벌로 가득찬

0개의 댓글