13. 라우터로 SPA 개발

✔ SPA

1. 전통적인 웹 페이지

  • 페이지 이동에 따른 html 받아와 로딩할 때마다 서버에 리소스 전달 받아 해석 후 화면에 보여줌
  • 사전에 html 파일 생성하거나 데이터에 따른 유동적인 html 생성하는 템플릿 엔진 사용
  • 화면 전환마다 html 서버에 요청 시 UI 상태 유지 어려움, 불필요한 로딩있어 비효율적임

2. SPA (Single Page Application)

  • 뷰 랜더링은 사용자 브라우저가 담당
  • 어플리케이션을 브라우저에 불러오고 실행 후 사용자 인터렉션 발생 시 필요한 부분만 js 사용해 업데이트

3. React Router

  • 다른 주소에 다른 화면 보여주기
  • 클라이언트 사이드에서 이루어지는 라우팅 간편하게 구현
  • 서버 사이드 랜더링 할 때 라우팅 도와주는 컴포넌트 제공
  • 규모가 커지면 js도 커지지만 code splitting 사용해 라우트별 파일 나눠 트래픽과 로딩 속도 개선

✔ 준비 & 사용법

1. 라우터 설치

yarn add react-router-dom@^5.3.0

2. 라우터 적용

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

import {BrowserRouter} from "react-router-dom";

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

3. 페이지 만들기

//Home
import React from 'react';

const Home=()=>{
    return (
        <div>
            <h1></h1>
            <p>, 그 페이지는 가장 먼저 보이는 페이지</p>
        </div>
    )
}

export default Home;
//About
import React from 'react';

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

export default About;

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

어떤 규칙을 가진 경로에 어떤 컴포넌트 보여줄지 정의
<Route path="주소 규칙" component={보여 줄 컴포넌트}>

import React from "react";
import { Route,Link } from "react-router-dom";

import Home from "./components/Home";
import About from './components/About';


const App = () => {
  return (
    <div>
      <Route path="/" component={Home} exact={true} />
      <Route path="/about" component={About}/>

    </div>
  );
};

export default App;
  • 기본 path인 path="/" exact={true}를 설정해야 /로 시작하는 라우팅과 겹치지 않음

exact=true 설정하지 않고 /about으로 페이지 이동을 한 경우 /와 /about로 설정한 것 동시에 등장

4. Link 컴포넌트 사용해 다른 주소로 이동

  • Link 컴포넌트를 사용해 페이지 전체 새로 불러오지 않고 HTML5 History API 사용해 페이지 주소만 변경함

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

import React from "react";
import { Route,Link } from "react-router-dom";

import Home from "./components/Home";
import About from './components/About';


const App = () => {
  return (
    <div>
      
      <ul>
        <li><Link to="/"></Link></li>
        <li><Link to="/about">about</Link></li>
      </ul>
      
      <Route path="/" component={Home} exact={true} />
      <Route path="/about" component={About}/>

    </div>
  );
};

export default App;
홈 클릭about 클릭

✔ Route 하나에 여러개 path 설정

  • /about/info 모두 about 페이지로 가도록 한 번에 path 설정
import React from "react";
import { Route,Link } from "react-router-dom";

import Home from "./components/Home";
import About from './components/About';


const App = () => {
  return (
    <div>
      
     <ul>
        <li><Link to="/"></Link></li>
        <li><Link to="/about">about</Link></li>
      </ul>
     
      <Route path="/" component={Home} exact={true} />
      <Route path={["/about","/info"]} component={About}/>

    </div>
  );
};

export default App;

✔ URL 파라미터와 쿼리

  • 유동적인 값으로 페이지 주소를 정의하는 경우
  • 쿼리 : 키워드를 검색하거나 페이지에 필요한 옵션 전달
  • 파라미터 : 특정 아이디 혹은 이름 사용해 조회하는 경우

1. URL 파라미터

  • 컴포넌트에서 받은 match 객체 안의 params 값 참조해 url 파라미터로 사용
  • path 규칙은 /profiles/:username일 때, match.params.username을 사용해 파라미터인 username값 조회 가능
import React from 'react';

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

const Profile=({match})=>{
    const {username}=match.params;
  //path 규칙에서 파라미터인 username 부분 받아오기
    const profile=data[username];
    if(!profile){
        return <div>존재하지 않는 사용자임</div>
    }
    return (
        <div>
            <h3>{username}({profile.name})</h3>
            <p>{profile.description}</p>
        </div>
    );
};

export default Profile;

import React from "react";
import { Route,Link } from "react-router-dom";

import Home from "./components/Home";
import About from './components/About';
import Profile from './components/Profile';


const App = () => {
  return (
    <div>
      
     <ul>
        <li><Link to="/"></Link></li>
        <li><Link to="/about">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;

2. URL 쿼리

쿼리 문자열을 객체로 변환하는 라이브러리 설치

yarn add qs

숫자 (?value=1) 또는 논리 자료형 (boolean) 사용 시 문자열 형태로 받아짐
숫자는 parseInt 사용해 변환 & 논리 자료형 값 사용해 "true"와 일치하는 지 확인


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;

✔ 서브 라우트

  • 라우터 내부에서 라우터 정의

App.js

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

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">HOME</Link>
        </li>
        <li>
          <Link to="/about">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} />
        /*Profiles 컴포넌트를 /profiles 경로에 연결*/
    </div>
  );
};

export default App;

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 사용해 보여주고 싶은 JSX 넣음

✔ 리액트 라우터 부가 기능

1. history

  • Route로 사용된 컴포넌트에 전달되는 props : match, location, 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 "./components/About";
import Home from "./components/Home";
import Profiles from "./components/Profiles";
import HistorySample from './components/HistorySample';

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">HOME</Link>
        </li>
        <li>
          <Link to="/about">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={HistorySample}/>
    </div>
  );
};

export default App;

2. withRouter

  • Higher-order Component
  • 라우팅 사용 컴포넌트 아니여도 match, location, history 객체 접근 가능
  • withRouter를 사용할 때 함수로 감싸 컴포넌트를 내보냄
  • withRouter 사용시 현재 자기를 기준으로 match 전달됨
    withRouter(WithRouterSample) : path="/profiles" 라고만 해서 username 파라미터 읽어온 것 없음
    withRouter(Profile) : path="/profiles/:username" 이라고 하여 username 파라미터 읽어옴

WithRouterSample.js

import React from 'react';
import { withRouter } from 'react-router-dom';
import Profiles from './Profiles';


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,3)}
                rows={7}
                readOnly={true}
            />
            <button onClick={()=>history.push("/")}>홈으로</button>
        </div>
    );
};

export default withRouter(WithRouterSample);

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;

Profiles.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 }) => {
    console.log(match)
  const { username } = match.params;
  console.log(username);
  const profile = data[username];

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

export default withRouter(Profile);

3. Switch

  • 여러 Route 감싸 그 중 일치하는 단 하나의 라우트만 랜더링 시켜줌
  • 모든 주소 규칙과 일치 하지 않는 경우 보여 줄 Not Found 페이지 구현 가능
import React from "react";
import { Route, Link, Switch } from "react-router-dom";
import About from "./components/About";
import Home from "./components/Home";
import Profiles from "./components/Profiles";
import HistorySample from './components/HistorySample';

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">HOME</Link>
        </li>
        <li>
          <Link to="/about">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}/>
        {/* path를 따로 정의 하지 않으면 모든 상황에 랜더링됨*/}
        <Route render={({location})=>(
          <div>
            <h2>이 페이지는 존재하지 않습니다.</h2>
            <p>{location.pathname}</p>
          </div>
        )}/>
      </Switch>
    </div>
  );
};

export default App;

4. NavLink

  • 현재 경로와 Link에서 사용하는 경로가 일치하는 경우 특정 스타일/CSS 클래스 적용 가능한 컴포넌트
  • 링크 활성화 되었을 때 스타일 적용하는 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">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;

좋은 웹페이지 즐겨찾기