본문 바로가기
React

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

by 해맑은 코린이 2021. 8. 3.

2021.08.03 리액트 라우터로 SPA 개발하기_정리노트

 

드디어....SPA 까지 왔다... 힘들어따......

 

SPA(Single Page Application)

말 그대로 한 개의 페이지로 이루어진 애플리케이션

 

기존에는 사용자가 다른 페이지로 이동할 때마다 새로운 html를 받아오고 페이지를 로딩할 때마다 서버에서 리소스를 받아서 해석한 뒤 화면에 보여준다. 사용자에게 보이는 화면은 서버가 제공함!! 그래서 사전에 html 파일을 만들어서 제공하거나, 데이터에 따라 유동적인 html 을 생성해주는 템플릿 엔진을 사용하기도 했다.

 

하지만 웹에서 제공하는 정보가 많아지면서 새로운 화면을 보여줄 때마다 모든 뷰를 준비하다보니 성능에 문제가 발생할 수 있어졌다.

예로 트래픽이 너무 증가한다거나, 사용자가 몰려 서버에 쉽게 높은 부하가 걸릴 수도 있다. 속도와 트래픽 측면에서는 캐싱(속도가 느린 디스크의 데이터를 속도가 빠른 메모리로 가져와서 메모리상에서 읽고 쓰는 작업)과 압축을 해서 서비스를 제공하면 어느정도 최적화 될 수 있겠지만, 사용자와 상호작용이 자주 발생하는 모던 웹 애플리케이션에서는 이 방법이 적당하지 않을 수 있다. 

 

이렇게 상호작용이 일어날 때마다 html 을 바꿔서 요청하면, 사용자의 인터페이스에서 사용하고 있던 상태를 유지하는 것도 번거롭고, 바뀌지 않는 부분까지 새로 불러와서 보여 주어야 하기 때문에 비효율적이 될 수 있다.

 

그래서 리액트 같은 라이브러리 혹은 프로엠워크를 사용해서 뷰 렌더링을 사용자의 브라우저가 담당하도록 하고, 우선 애플리케이션을 브라우저에 불러와서 실행시킨 후에, 사용자와의 인터렉션이 일어나면 필요한 부분만 자바스크립트를 사용하여 업데이트 해준다. 

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

 

단순히 싱글 페이지라고 해서 화면이 한 가지 종류 ? 

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

블로그를 예로 들면 블로그의 한 페이지에서 홈,CRUD 등을 할 수 있도록 하는 것!! 한 페이지에서 다른 URL 주소를 통해서 작업을 하는 것이라고 생각하면 된다. 이를 라우팅이라 한다.

 

리액트 라이브러리에서는 라우팅이 자체적으로 내장되어 있지는 않지만, 브라우저의 API 를 직접 사용하여 이를 관리하거나, 라이브러리를 사용하면 이 작업을 쉽게 할 수 있다. (리액트 라우터, 리치 라우터, Next.js 등등 )

 

오늘은 리액트 라우터를 사용해봅시다! 해당 라이브러리는 클라이언트 사이드에서 이루어지는 랑팅을 아주 간단하게 구현할 수 있도록 해주고, 더 나아가서 서버 사이드 렌더링을 할 때도 라우팅을 도와주는 컴포넌트들을 제공해준다. 

 

이 쯤에서 개념을 정리하면서 어려운 용어들이 톡톡 튀어나오는 거..같은데 일단 서버사이드 렌더링 정리 해보자!

 

 

서버 사이드 렌더링 (SSR)

페이지를 이동할 때마다 새로운 페이지를 요청하는 것.

모든 템플릿은 서버 연산을 통해서 렌더링하고 완성된 페이지 형태로 응답한다. 

 

근데 아까 분명히 리액트에서는 이렇게 안한다고 했음.

 

그게 바로

 

클라이언트 사이드 렌더링 (CSR)

클라이언트에서 렌더링하는 방식

첫 요청시 한 페이지만 불러와 사용자의 행동에 따른 필요한 부분만 다시 읽어들이기 때문에 서버 측에서 렌더링하여 전체 페이지를 새로 띄우는 것보다 빠른 인터렉션을 기대할 수 있다. 리로딩 없이 서버로 부터 받아서 화면을 갱신하기만 하면 된다.

 

 

리액트는 그래서 인터렉티브한 SPA 를 만들기에 아주 최적화 되어 있는데, 왜 서버 사이드 렌더링 개념이 툭 튀어나온 것일까!

 

앱의 규모가 커지면 자바스크립트 파일이 너무 키지기 때문에, 페이지 로딩 시 사용자가 실제로 방문하지 않을 수도 있는 페이지의 스크립트도 불러오기 때문이다. 또한 해당 페이지에서 backend 서버에 어떤 데이터를 불러오는 작업까지 있어야 한다면..! 이또한 과정이 복잡해지며 성능이 저하될 수 있다. 

가장 큰 단점은, 자바스크립트를 실행하지 않는 일반 크롤러에서는 페이지의 정보를 제대로 수집해 가지 못하기 때문에 구글,네이버,다음 같은 검색 엔진의 검색 결과에 페이지가 잘 나타나지 않는다는 단점이 존재...

그렇기 때문에 서버 사이드 렌더링이 필요한 시점이 나타난다. 책을 따라서 다음에 이를 해결해보자 ㅎㅅㅎ...!!

 

ㅋㅋㅋㅋ지금은 개념을 잘 몰라서 찾아봤는데, 나중에 이 방법을 통해 가능한 빠르게 유저에게 로딩된 페이지를 보여줌과 동시에, React 어플리케이션의 강점을 다 가져갈 수 있단다..!!

 

 

 

옛날엔 프로젝트하면서 뭣도 모르고 그냥 링크같은 개념으로 썼었는데, 이 기회에 확실히.. 깨달았군 큼큼

 

굿굿 이제 진짜 react-router 를 사용해보러 ㄱ ㄱ

 

 

$ yarn create react-app router-tutorial

$ cd router-tutorial

$ yarn add react-router-dom

 

 

src/index.js

 

오옹 라우터는 이렇게 index.js 에서 감싸는군. 

BrowserRouter 라는 컴포넌트는 웹 어플리케이션에서 HTML5의 History API 를 사용하여 페이지를 새로고침하지 않고!!!! 주소를 변경하고, 현재 주소 관련의 props 로 쉽게 조회하거나 사용할 수 있도록 해준다.

 

 

이제 라우트로 띄울 페이지 두 개를 생성해서, App 에서 설정해줘봅시단.

 

 

src/Home.js (새로 생성)

 

 

src/About.js (새로 생성)

 

 

 

src/App

 

Route 라는 컴포넌트를 사용해서, 현재 경로에 따라 다른 컴포넌트를 보이게 했다.

 

 

 

갸악 신기하군 이제 about 으로 들어가면 어바웃 컴포넌트가 보이겠지 ?

 

 

 

롸...? 왜 홈까지 같이 보이는거지

 

그거슨 /about 으로 들어가면 / 도 들어가있기 때문에 발생한 현상이다 이를 수정하려면, exact 라는 props 를 true 로 설정해주자.

 

 

src/App

 

 

요로코롬 설정해주면, 

 

 

크으 요로코롬!

 

 

이제 사용자가 클릭하면 해당 주소로 이동시켜주는 컴포넌트를 만들어야겠지 그게 바로 Link! 

html 의 a 태그와 같은 역할이라고 보면 된다. 

리액트 라우터를 사용할 때는 이 태그를 직접 사용하면 페이지를ㄹ 전환하는 과정에서 페이지를 새로고침해서 새로 들고 오기 때문에 애플리케이션이 돌고 있던 상태들을 모두 날려버리게 된다. 그러면 렌더링된 컴포넌트들도 모두 사라지고 다시 처음부터 렌더링하게 된다.

 

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

 

 

음 그래서 또 HTML5 History API  가 뭐지.

 

History API 

SPA 라우터에서 사용하고 있는 주소 API . 

이 API 는 기존 window.history 를 그대로 활용한다.

주소 내역은 하나의 목록이다. 목록에 새로운 주소를 추가하면 페이지를 이동한 셈이 된다. 이 추가하기 위한 메소드가 HTML5 에서 생겼다.

 

https://www.zerocho.com/category/HTML&DOM/post/599d2fb635814200189fe1a7

 

(HTML&DOM) History API - 주소를 내 마음대로!

안녕하세요. 이번 시간에는 History API에 대해 알아보겠습니다. 제 블로그를 보시면 페이지가 깜빡이지 않는데도 내용도 바뀌고 주소도 바뀝니다. 물론 리액트 기술을 사용하였기 때문에 가능한

www.zerocho.com

 

여기서 내가 원하는 내용만 아주아주 간단하게 뽑아옴 ㅎㅅㅎ 쨋든 이 기능을 사용해서 라우터로 주소 목록을 추가해준 것!

 

 

src/App

 

이렇게 Link 컴포넌트를 사용하면, 

 

 

 

 

이렇게 잘 넘어간당!!!

 

이렇게 a 로 이루어져 있는데 실행해보니까 새로고침 없이 바로 톡톡 해당 컴포넌트가 뜬다 넘무 신기... 

 

 

 

또 리액트 v5 부터는 여러개의 path 를 배열에 담아서 하나의 컴포넌트를 보여줄 수 있다. 

 

 

 

 


페이지 주소에

/about?details=true 와 같은 유동적인 값을 전달해야할때는 어떻게 해야할까.

 

이 때는 파라미터와 쿼리로 나뉘는데, 무조건 따라야할 규칙은 없다.

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

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

 

URL 파라미터

Profile 컴포넌트를 만들어서 파라미터를 사용해볼텐데, 

profile/korin 처럼 뒷부분에 username 을 유동적으로 props 로 받아와서 조회할 수 있움. 

 

 

좀 헷갈려서 이것저것 찾아보며 흐름을 혼자 정리해보았다.

 

 

src/App

먼저 props 를 :key 값의 형태로 설정값을 적어주었다.이렇게 라우트를 정의한 후, 각 프로필에 맞는 경로를 Link 로 추가해주었다. 

:username 이라거 넣어주면, match.params.username 으로 value 값을 접근할 수 있다. 현재 username 값을 조회할 수 있다는 말씀! 

 

 

match.params.username ..? 무슨 말이지 . 그건 이제 다음에 작성할 Profile 에서 조회할 수 있도록 해줄 것이다. ㄱ ㄱ

 

src/Profile.js ( 새로 생성 )

 

import React from 'react';


const data = {
    korin:{
        name:'korin',
        description:'hello react'
    },
    note:{
        name:'note',
        description:'hello router'
    }
};


const Profile = ({match}) => {
    // match 라는 객체 안의 params 값을 참조. match 객체 안에는 현재 컴포넌트가 어떤 경로 규칙에 의해 보이는지에 대한 정보가 들어있음.
    //  key 값으로 설정해준 username 으로 객체 비구조화 할당
    
    const { username } = match.params ;
    
    // username 에 따른 profile 객체를 반환
    const profile = data[username];
    if(!profile){
        return <div> 존재하지 않는 사용자. who are you </div>
    }
    return (
        <div>
            <h3>{username}({profile.name})</h3>

            <p>{profile.description}</p>
            
        </div>
    );
};

export default Profile;

https://reactrouter.com/web/api/match

 

React Router: Declarative Routing for React

Learn once, Route Anywhere

reactrouter.com

 

이렇게 match 객체를 사용하면 해당 객체의 params 값을 참조할 수 있다. 

 

console.log(match.params)

 

이렇게! 요 안에는 현재 컴포넌트가 어떤 경로 규칙에 의해 보이는지에 대한 정보가 들어있음.

 

우리는 여기서 해당 객체에서 라우팅 시 지정한 각각의 key 값( username )에 대한 원하는 데이터를 객체로 반환한다.

 

console.log(username) 과 console.log(profile)

 

그래서 profile.name 으로 korin 의 이름이나 note 의 이름을 조회해서 띄워줄 수 있다. 

 

 

모를땐 역시 찍어보는게 장땡 ㅎㅅㅎ

 

 

 

 

 

 

 

실행화면

 

 

 

 

굿 그러면 해당 경로에 해당된 프로필만 잘 보인다!!

 

 

URL 쿼리

쿼리는 location 객체에 들어있는 search 값에서 조회할 수 있다. location 객체는 라우트로 사용된 컴포넌트 (자식 컴포넌트. 여기서는 About 페이지를 예로 들 것임.) 에게 props 로 전달되며,  웹 애플리케이션의 현재 주소에 대한 정보를 지니고 있음.

 

 

location 형태

 

{
	"pathname" : "/about",
	"serach" : "?detail=true",
	"hash':"",

}

 

요 location 객체는 http://localhost:3000/about?detail=ture 주소로 들어갔을 때 값인데, 여기서 serach 를 가져와야 한다. 

이 값은 문자열로 구성되어 있는데, ?detail=true&another=1 과 같이 여러가지 값을 설정해줄 수 있음

근데 이 search 값을 읽어 오기 위해서는 객체형태로 변환해주어야 하는데, 쿼리 문자열을 객체로 변환할 때 쓰는 라이브러리가 있다.

 

qs 라는 라이브러리를 사용한다네 역시 설치해주장~

 

$ yarn add qs

 

src/About

import React from "react";
// qs import
import qs from "qs";

const About = ({ location }) => {
  const query = qs.parse(location.search, {
    ignoreQueryPrefix: true, // 이 설정을 통해서 문자열 맨 앞의 ?를 생략할 수 있음.
  });

  const showDetail = query.detail === 'true'  // 쿼리의 파싱 결과 값은 문자열 
//   결과값은 언제나 문자열이기 때문에 숫자나 불리언형을 넣어도 문자열 형태로 받아옴. 그렇기 때문에 숫자형이 필요하면 parseInt 와 같은 함수를 통해 꼭 변환해주고 , 지금과 같은 true or false 불리안형 값을 사용해야 하는 경우에는 true 문자열과 일치하는지 비교 필요.
  return (
    <div>
      <h1>About Korin</h1>
      <p>프로젝트 소개를 책에서는 하고 있지만, 나는 내 소개를 할 것임. </p>
      <p>코린이는 지금 몹시 배가 고프다.</p>
      {showDetail && <b>detail 값이 true</b>}
    </div>
  );
};

export default About;

 

쿼리스트링에서는 라우팅하는 과정에서 /about 까지만 라우팅해서 나머지는 About 컴포넌트에서 따로 쿼리스트링을 요청해서 정보를 보내기 때문에, 부모 컴포넌트에서 라우팅 하는 과정은 따로 없다!

 

 

 

이렇게 적어주고 url 에서 http://localhost:3000/about?detail=true 라고 입력해주게 되면, 

 

 

 

이렇게 true 값에 대해 설정한 문구가 잘 보인다!!!

 

 


 

라우트 내부에 또다른 라우트를 연결할 때는 ?

 

서브라우트 개념! 서브라우트는 간단하게 작업할 수 있는데, 그냥 라우트로 사용되고 있는 컴포넌트의 내부에 Route 컴포넌트를 또 사용하면 된다.

 

그러면 지금 두 가지의 프로필을 보여주고 있는 Profile 컴포넌트를 바로 가기전에, 프로필 링클로 갈 수 있는 Profiles 컴포넌트를 새로 생성해서 그 안에서 각각의 프로필을 보여줄 수 있도록 실습 같이 ㄱ ㄱ 

 

 

src/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/korin">korin</Link>
                </li>
                <li>
                    <Link to="/profiles/note">note</Link>
                </li>
            </ul>
            {/* render props 를 넘겨서 컴포넌트 자체를 전달하는 것이 아니라, 보여주고 싶은 JSX를 넣어줄 수 있음 */}
            <Route 
            path="/profiles"
            exact 
            render={()=> <div>사용자를 선택해주세요.</div>}
            />
            <Route path="/profiles/:username" component={Profile} />
        </div>
    );
};

export default Profiles;

지금 보면 첫번째 Route 컴포넌트에 render props 를 넣어주었는데, 얘는 컴포넌트 자체를 전달하는 것이 아니라 보여주고 싶은 div 태그 자체를 적어주었다.

지금처럼 따로 컴포넌트를 만들기 애매한 상황에 사옹해도 되고, 컴포넌트에 props 를 별도롤 넣어주고 싶을 때도 사용 가능! 

 

 

그리고 username 에 따른 profile 을 보여주는 Route를 두번째로 적어주었음.

 

 

JSX 에서 props 를 설정할 때 값을 생략하면 자동으로 true로 설정된다. exact 나 exact={true} 나 같은 뜻으로 사용된다!

 

src/App

 

 

그리고 App 에서는 Profiles 를 띄우도록 수정해준다..!!

 

 

 

실행화면

 

프로필을 누르면 사용자를 선택하달라는 문구와 함께 프로필 목록이 뜨고, 

 

 

각 사용자의 프로필을 누르면 각 사용자별로 프로필이 잘뜬다!

 


 

 

후.. 이제 끝나가...! 부가 기능들 소개하며 마무리해보자

 

history

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

 

특정 버튼을 눌렀을 때 바로 뒤로 간다거나, 로그인 후 화면을 전환하거나 다른 페이지로 이탈하는 것을 방지해야할 때 쓰면 되는 기능!!

 

 

 

요롷게 history 객체를 찍어보면, 여러가지 기능들이 많은데 오늘은 뒤로 갈 때 쓰는 goBack(), 원하는 컴포넌트로 이동할 수 있는  push() 를 써볼 것임. 참고로 history.push('이동하고자 하는 경로') 의 형태로 쓴다.

 

 

 

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

 

여기서는 컴포넌트가 마운트되고, 언마운트될 때마다 뭔가를 하기 위해서 class 형 컴포넌트를 씀 . 이렇게 적어주면 해당 페이지를 떠나려고 할 때마다 이탈 방지 메세지가 뜬다

 

src/App

 

 

앱에 라우트와 링크를 연결해주면!!!!!

 

 

실행화면

 

버튼을 누르게 되면 이탈 방지 메세지가 잘 뜬다 홀홀

 

 

withRouter

해당 함수는 Hoc (Higher-order Component) 이다. 해당 컴포넌트는 컴포넌트 로직을 재사용하기 위한 React 의 고급기술이래 ㅎㅅㅎ ..! React API 의 일부가 아니고 리액트에서 구성적 특성에서 나오는 패턴인데, 쨋든 이 함수를 쓰게 되면 match,location,history 객체에 접근할 수 있게 해줌!!

 

더 자세한 설명은 공식문서 주욱 읽어보기! 나는 주욱 읽고 이해되는 부분만...그러려니 하고 넘어감 ㅎㅅㅎ

 

 

src/WithRouteSample.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>
  );
};

// withRouter 함수로 감싸서 보내줌.

export default withRouter(WithRouterSample) ;

 

여기서 Json.stirngify 에 null,2 를 쓰면 컴포넌트를 내보내줄 때 들여쓰기 2칸이 되어서 이쁘게 나온다!!

또 withRouter 를 사용할 때는 컴포넌트를 내보낼 때 withRouter 함수로 감싸서 보내주면 된다!

 

 

 

 

이렇게 해당 객체가 잘 뜨는데, 여기서 match 객체에서 params 가 비어있는데, withRouter 를 사용하게 되면 현재 자신을 보여주고 있는 라우트 컴포넌트를 기준으로 ( 여기서는 Profiles ) match 가 전달된다. Profiles 를 위한 라우트를 설정할 때는 path="/profiles" 라고만설정했기 때문에 username 을 읽어오지 못해서 빈 값이 출력된다. 

 

 

 

src/Profile

 

그래서 Profile 로 가서 WithRouterSample 컴포넌트를 넣으면,

 

 

이렇게 username 파라미터를 잘 받아오는 모습을 볼 수 있음!

 

 

 

 

Switch

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

얘를 사용하면, 모든 페이지에 일치하지 않을 때 띄우는 Page Not Found 를 띄워줄 수 있다!

 

 

 

 

 

이렇게 Switch 컴포넌트로 감싸주고, 따로 path 를 정의하지 않았을 모든 경우에 해당 location pathname 과 함께 이 페이지는 존재하지 않는다고 뜬다.

 

 

 

 

실행 화면

 

이렇게!

 

 

 

 

 

 

 

 

 

NavLink

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

 

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

 

 

src/Profiles

 

 

이렇게!!!! 

 

 

 

 

 

해당 선택된 프로필에 색상이 검정과 흰색으로 스타일링 완성!

 

 

 


어후.....이틀이....걸렸다.....물론 중간중간 놀면서 하긴했지만 역시 하나의 컴포넌트 안에서는 그래도 음음 하는데..... 컴포넌트 간의 이동이 많아지면 갑자기 헷갈리기 시작하면서....증맬루....후.... 근데 또 이렇게 해서 프로젝트 규모가 커지면 여러가지 문제가 발생한단다... 최종 결과물에 불필요한 컴포넌트를 불러오는 것들이 많아지기 때문.. 이것또한 책에서 다룬다고 하니 다음에 또 정리해야지!!!!!

 

 

오늘의 포스팅 끝끝!!!!

'React' 카테고리의 다른 글

2021.08.08 React Context API  (2) 2021.08.08
2021.08.07 React 외부 API 호출  (0) 2021.08.07
2021.07.30 React Todo-List 성능 최적화, immer  (0) 2021.07.30
2021.07.28 React To-do List  (0) 2021.07.29
2021.07.24 React styling  (2) 2021.07.24

댓글