본문 바로가기

코드스테이츠 SEB FE 41기/Section 별 내용 정리

section2/unit5/[React]React SPA(9/28)

반응형

블로깅 주제

  • React SPA

1. 지금 현재, 당신의 기분이나 느낌을 표현해 주세요.

  • 비동기와 리액트로 화려하게 장식된(?) 이번주의 반 이상을 넘기고 목요일이 되었다. 리액트는 처음이라 아직 감이 잘 잡히지 않는다. 그래도 익숙해지면 매우 편하다고 하니 내심 기대가 된다. 얼른 리액트에 익숙해져서 자유자재로 쓰게되는 날이 오면 좋겠다. 처음은 다 어려운거다. 처음엔 다 헷갈린다. 이렇게 생각하고 마음을 편하게 먹어야겠다. 오늘이 금요일이였으면 좋겠다!! ㅋㅋㅋ 

2. 오늘 무엇을 학습한 내용 중 지금 떠올릴 수 있는 단어를 모두 나열해 주세요.

  • React SPA, React Router

3. 2에서 작성한 단어를 가지고, 오늘의 학습 내용을 설명해 보세요.

- React SPA 

▶ SPA의 등장개념과 배경

전통적인 웹사이트에서는 사용자가 웹사이트 내의 다른 페이지로 이동하면, 브라우저가 페이지를 보여주기 위해 매번 HTML 파일로 된 "페이지 전체"를 불러와야만 했다.

예시 그림은 동일한 페이지 전환을 보여주고 있다.

전통적인 웹사이트는 페이지 전체를 로딩하고, SPA는 Menu와 Footer와 같이 페이지 전환 전후에 중복되는 부분은 새로 불러오지 않는다. 전통적인 웹사이트에서는 이와 같이 "페이지 전체를 불러오는 행위"를 보통 깜빡인다고 표현한다.

웹사이트가 보다 복잡해지고 애플리케이션의 형태를 가지게 되면서, 사용자와 서비스 사이에 더욱 많은 상호작용이 일어나게 되었다. 하지만 이때마다 Header나 Navigation Bar 등과 같이 중복되는 요소들을 매번 불러오는 것이 서버와의 불필요한 트래픽을 발생시켰다. 한편, 사용자 입장에서는 매번 모든 페이지를 불러옴에 따라 더 느린 반응성을 갖게 되었고, 이는 애플리케이션과 같은 사용자 경험을 제공하기 어렵게 만들었다.

1990년대 후반에 HTML 문서 전체가 아닌, 업데이트에 필요한 데이터만 서버에서 전달받아 이 데이터를 JavaScript가 동적으로 HTML 요소를 생성해서 화면에 보여주는 방식이 개발되어 사용되기 시작하였다. 2000년대 중반부터 이러한 개발 방식을 이용한 웹 애플리케이션이 보편화되었으며, 이것이 우리가 지금 배우고 있는 싱글 페이지 애플리케이션, 즉 SPA이다.

SPA는 서버로부터 완전히 새로운 페이지를 불러오는 것이 아니라, 화면을 업데이트하기 위해 필요한 데이터만 서버에서 전달받아 브라우저에서 해당하는 부분만 업데이트하는 방식으로 작동하는 웹 애플리케이션이나 웹 사이트를 말한다.

 

▶ SPA의 장점

 

 SPA의 단점

1) 첫 화면 로딩 시간 길어짐

브라우저는 첫 화면 로딩 시에 HTML 파일을 읽어들인 후 그 안의 script 요안에 있는 JavaScript 파일을 다시 받아오는 과정을 거친다.

이때 첫 화면 로딩 시 읽어들인 HTML 파일은 거의 비어있고, 대부분의 코드는 JavaScript 파일 안에 들어있다 보니 자연스럽게 JavaScript 파일이 무거워진다. 때문에 이 JavaScript 파일을 기다리는 시간으로 인해 첫 화면의 로딩 시간이 길어진다.

2) 검색 엔진 최적화가 좋지 않음

검색엔진 최적화란 구글이나 네이버 같은 검색엔진이 자료를 수집하기 좋도록 웹 페이지를 구성하는 것을 뜻한다. 여기서 검색 엔진의 작동 방식을 잠깐 알아보면, 검색 로봇이 웹 페이지에 있는 정보를 수집하고 분석해서 그 결괏값에 인덱스를 만들어 보관하고 있다가 사용자가 검색어를 입력하면 보관하고 있던 인덱스에서 검색어와 가장 연관성이 높은 웹 페이지들을 순서대로 보여주는 방식으로 작동한다.

검색 로봇은 자료를 수집할 때에 웹 페이지의 URL은 물론이고 HTML 문서 내의 각종 태그나 링크 등을 분석한다. SPA는 HTML이 거의 비어있다 보니 검색 로봇이 충분한 자료를 수집하지 못한다. 이 때문에 검색 노출이 중요한 웹 애플리케이션은 검색 엔진 최적화에 대한 대응책을 따로 마련해야 하고, 더불어 앱 안에서 브라우저의 앞으로 가기/뒤로 가기 등의 상태 관리도 해야 하기 때문에 개발의 복잡도가 더욱 늘어난다. 다만 SPA에서도 검색 엔진 최적화에 대응할 수 있도록 검색 엔진이 발전하고 있어서, 점차 이 단점은 사라지고 있는 추세이다.


- Wireframe

Wireframe은 디자인에 들어가기 전 단계로 선(wire)를 이용해 윤곽선(frame)을 잡는 것을 말한다. 이 작업을 통해 개발자는 디자인 컨셉과 사이트 기능에 대한 이해를 할 수 있다. 

목업(mockup)은 데스크톱, 스마트폰의 프레임을 덧씌워 직관적으로 이해하기 쉽게 디자인한 것을 말한다.

 

예시) 유튜브

가장 작은 단위의 컴포넌트를 한번 분석해보자.

한눈에도 다양한 정보를 담고 있는 Content 컴포넌트를 한번 살펴보자. Content 컴포넌트는 상단에는 썸네일, 중앙에는 아바타와 영상 소개 글, 하단에는 채널 이름과 조회 수, 업로드한 날짜가 기재되어 있다.

Content 컴포넌트는 영상과 관련된 데이터를 입력받아, UI에 맞게 화면에 출력해준다. 더불어 눈에 보이지는 않지만 클릭 시 해당 영상을 재생해 주는 기능도 가지고 있다.

이 데이터는 영상이 대기 목록에 있을 때도, 혹은 재생 중일 때도 동일한 내용이 화면에 출력된다. 어떤 상태로 있느냐에 따라 출력되는 위치만 조금씩 달라질 뿐이다.

이처럼 애플리케이션 안에서 다뤄지는 데이터를 컴포넌트들끼리 보다 유기적으로 주고받을 수 있도록 설계해야 한다. React Router를 사용한다면 컴포넌트 활용이 가능하다.


- React SPA : 경로에 따라 다른 뷰를 보여줌

예를 들어  twittler와 같은 SPA를 만들 때 , 메인 트윗 모음 페이지, 알림 페이지, 마이 트윗 페이지 등의 화면이 필요할 수 있다.

또한 이 화면에 따라 "주소"도 달라진다.

이렇게 다른 주소에 따라 다른 뷰를 보여주는 과정을 "경로에 따라 변경한다."라는 의미로 라우팅(Routing)이라고 한다.

React SPA에서는 라우팅을 위해 React Router라는 라이브러리를 가장 많이 사용한다.

 

▶ React Router 활용

▷ React Router의 주요 컴포넌트

  1. BrowserRouter : 라우터 역할
  2. Routes, Route :  경로 매칭
  3. Link : 경로 변경

 

이 컴포넌트들을 사용하기 위해서는 React Router 라이브러리에서 따로 불러와야 한다. 다음 명령어를 통해 사용할 수 있다.

Import는 필요한 모듈을 불러오는 역할로 비구조화 할당(destructuring assignment)과 비슷하게 이용할 수 있다.

 

▶ React Router 사용환경 세팅

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

 

- React Router 튜토리얼

BrowserRouter

<BrowserRouter> 컴포넌트는 웹 애플리케이션에서 HTML5의 History API를 사용해 페이지를 새로고침하지 않고도 주소를 변경할 수 있게 해준다. 또한 <BrowserRouter> 가 상위에 작성되어 있어야 React Router의 컴포넌트들을 사용할 수 있다.

 

Routes, Route

경로를 매칭해주는 역할을 하는 컴포넌트이다.

  • <Routes> 컴포넌트는 여러 <Route> 컴포넌트를 감싸서 그중 경로가 일치하는 단 하나의 라우터만 렌더링을 시켜주는 역할을 한다. <Routes> 를 사용하지 않으면 매칭되는 모든 요소를 렌더링한다.
  • <Route> 컴포넌트는 path 속성을 지정하여 해당 path 에서 어떤 컴포넌트를 보여줄지 정한다. \<Link> 컴포넌트가 정해주는 URL 경로와 일치하는 경우에만 작동된다.

※ 만약 사용자가 지정된 주소인 “/’, “/mypage”, “/dashboard” 이외의 주소로 접근하게 되면 ?

의도한 화면이 보이지 않을 수 있다. 이럴 때 사용할 수 있는 속성이 path=”*” 이다. 지정되지 않은 주소로 접근할 시에는 이 속성이 설정되어 있는 컴포넌트를 보여주게 된다.

 

Link

경로를 연결해 주는 역할을 하는 컴포넌트이다. 페이지 전환을 통해 페이지를 새로 불러오지 않고 애플리케이션을 그대로 유지하여 HTML5 History API를 이용해 페이지의 주소만 변경해 준다.

ReactDOM으로 렌더를 시키게 되면 <Link> 컴포넌트는 <a> 요소로 바뀌는 모습을 볼 수 있다.

React Router에서 <a> 요소가 아닌 <Link>를 사용하는 이유가 있나요?

<a> 요소는 페이지를 전환하는 과정에서 페이지를 불러오기 때문에 다시 처음부터 렌더링을 시킨다. 즉, 새로고침 현상이 일어나게 된다. 하지만 <Link> 컴포넌트는 페이지 전환을 방지하는 기능이 내장되어 있기 때문에 SPA를 구현할 수 있다.

 

실습)

function App() {
  return (
    <BrowserRouter>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>{/* Link 컴포넌트를 이용하여 경로를 연결합니다 */}
            </li>
            <li>
              <Link to="/mypage">MyPage</Link>
            </li>
            <li>
              <Link to="/dashboard">Dashboard</Link>
            </li>
          </ul>
        </nav>

         <Routes>
          <Route path="/" element={<Home />} /> 
		{/* 경로는 path로 컴포넌트는 element로 연결해 줍니다. */}
          <Route path="/mypage" element={<MyPage />} /> 
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </div>
    </BrowserRouter>
  );
}

function Home() {
  return <h1>Home</h1>;
}

function MyPage() {
  return <h1>MyPage</h1>;
}

function Dashboard() {
  return <h1>Dashboard</h1>;
}

export default App;

 

페이지가 새로고침없이 바뀜


- 과제 React twittler SPA

기존 React Twittler Intro 에 React Router 기능을 담은 Twittler 로 발전시킨다.

 

▶ Bare Minimum

App.js

import React from 'react';
import './App.css';
import {BrowserRouter, Route, Routes} from "react-router-dom";
// TODO - react-router-dom을 설치 후, import 구문을 이용하여 BrowserRouter, Route, Switch 컴포넌트를 불러오세요.

import Sidebar from './Sidebar';
import Tweets from './Pages/Tweets';
import About from './Pages/About';
import MyPage from './Pages/MyPage';

// TODO - import문을 이용하여 MyPage, About 컴포넌트를 불러오세요.

const App = () => {
  return (
    <div>
      {/* TODO - BrowserRouter 컴포넌트를 작성합니다. */}
      <BrowserRouter> 
      <div className="App">
          <main>
            <Sidebar />
            <section className="features">
            <Routes>
              <Route path="/" element={<Tweets />} /> 
              <Route path="/about" element={<About />} />
              <Route path="/mypage" element={<MyPage />} /> 
            </Routes>
            </section>
          </main>
        </div>
        </BrowserRouter>
    </div>
  );
};

// ! 아래 코드는 수정하지 않습니다.
export default App;

Sidebar.js

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

const Sidebar = () => {
  return (
    <section className="sidebar">
    {/* About 메뉴 아이콘과 Mypage 메뉴 아이콘을 작성하고 Link 컴포넌트를 이용하여 경로(path)를 연결 */}
      <Link to="/">
        <i className="far fa-comment-dots"></i>
      </Link>
      <Link to="/about">
        <i className="far fa-question-circle"></i>
      </Link>
      <Link to="/mypage">
        <i className="far fa-user"></i>
      </Link>
    </section>
  );
};

export default Sidebar;

Tweet.js

import React from 'react';
import { dummyTweets } from '../static/dummyData';
import './Tweets.css';
// ! 위 코드는 수정하지 않습니다.

// TODO - import문을 이용하여 Footer 컴포넌트를 불러오세요.
import Footer from '../Footer';

const Tweets = () => {
  return (
    <div>
      <div className="tweetForm__container">
        <div className="tweetForm__wrapper">
          <div className="tweetForm__input">
            <div className="tweetForm__inputWrapper">
              <div className="tweetForm__count" role="status">
                <span className="tweetForm__count__text">
                  {'total: ' + dummyTweets.length}
                </span>
              </div>
            </div>
          </div>
        </div>
      </div>
      <ul className="tweets">
        {dummyTweets.map((tweet) => {
          return (
          <li className="tweet" key={tweet.id}>
            <div className="tweet__profile">
              <img src={tweet.picture} />
            </div>
            <div className="tweet__content">
              <div className="tweet__userInfo">
                <span className="tweet__username">{tweet.username}</span>
                <span className="tweet__createdAt">{tweet.createdAt}</span>
              </div>
              <div className="tweet__message">{tweet.content}</div>
            </div>
          </li>
          )
        })}
      </ul>
      {/* TODO - Footer 컴포넌트를 작성합니다. */}
      <Footer />
    </div>
  );
};


export default Tweets;

Mypage.js

import React from "react";
import Footer from "../Footer";
import { dummyTweets } from "../static/dummyData";
import "./MyPage.css";
// ! 위 코드는 수정하지 않습니다.

// TODO - import문을 이용하여 Footer 컴포넌트를 불러옵니다.

const MyPage = () => {
  // TODO - filter 메소드를 이용하여 username이 kimcoding인 요소만 있는 배열을 filteredTweet에 할당합니다.
  const filteredTweets = dummyTweets.filter(obj => obj.username === 'kimcoding');

  return (
    <section className="myInfo">
      <div className="myInfo__container">
        <div className="myInfo__wrapper">
          <div className="myInfo__profile">
            <img src={filteredTweets[0].picture} />
          </div>
          <div className="myInfo__detail">
            <p className="myInfo__detailName">
              {filteredTweets[0].username} Profile
            </p>
            <p>28 팔로워 100 팔로잉</p>
          </div>
        </div>
      </div>
      <ul className="tweets__mypage">
        {/* TODO : dummyTweets중 kimcoding 이 작성한 트윗 메세지만 있어야 합니다. */}
        <li className="tweet" key={filteredTweets[0].id}>
          <div className="tweet__profile">
            <img src={filteredTweets[0].picture} />
          </div>
          <div className="tweet__content">
            <div className="tweet__userInfo">
              <span className="tweet__username">{filteredTweets[0].username}</span>
              <span className="tweet__createdAt">{filteredTweets[0].createdAt}</span>
            </div>
            <div className="tweet__message">{filteredTweets[0].content}</div>
          </div>
        </li>
      </ul>
      <Footer />
    </section>
  );
};

export default MyPage;

 

 

▶ Advanced

useNavigate 를 이용하여 뒤로가기 기능 만들기

import React from 'react';
import { Link } from "react-router-dom";
import { useNavigate } from 'react-router-dom';

const Sidebar = () => {
  const navigate = useNavigate();
  const handleGoBack = () => {
		navigate(-1);
	}

  return (
    <section className="sidebar">
    {/* About 메뉴 아이콘과 Mypage 메뉴 아이콘을 작성하고 Link 컴포넌트를 이용하여 경로(path)를 연결 */}
      <Link to="/">
        <i className="far fa-comment-dots"></i>
      </Link>
      <Link to="/about">
        <i className="far fa-question-circle"></i>
      </Link>
      <Link to="/mypage">
        <i className="far fa-user"></i>
      </Link>
      <button onClick={handleGoBack}><i className="fas fa-arrow-circle-left"></i></button>
    </section>
  );
};

export default Sidebar;

 

 

반응형