이번 시간에는 SPA(Single Page Application)의 등장 배경과 개념, 그리고 장단점에 대해 알아보자.
전통적인 웹사이트는 사용자가 다른 페이지로 이동할 때마다, 브라우저가 새로운 HTML 파일을 불러와서 페이지 전체를 로딩해야 했다.
이러한 방식은 중복되는 요소들을 매번 불러오는 등 불필요한 트래픽을 발생시키고, 사용자가 페이지를 이동할 때마다 모든 페이지를 로딩해야 하기 때문에 느린 반응성을 가지게 되어 사용자 경험을 저하시키는 문제가 있었다.
그래서 1990년대 후반에는 필요한 데이터만 서버에서 전달받아 JavaScript가 동적으로 HTML 요소를 생성해서 화면에 보여주는 방식이 개발되었고, 2000년대 중반부터는 이러한 방식을 이용한 웹 애플리케이션이 보편화되면서 SPA라는 개념이 등장하게 되었다.
예시
유튜브를 생각해보면, 홈 화면에서 영상 목록을 보는 동안에도 댓글을 달거나 다른 영상으로 이동할 수 있다. 이때 SPA 방식으로 만들어진 유튜브는, 사용자가 댓글을 달거나 다른 영상을 클릭할 때, 필요한 부분만 로드해서 업데이트하게 된다. 그래서 사용자는 화면이 깜빡이거나 새로고침되는 것 없이 부드럽게 이동하고 업데이트된 정보를 확인할 수 있다.
예시
게임을 하려고 할 때, 게임을 위한 JavaScript 파일이 너무 크면 게임을 시작하기 전에 시간이 많이 걸리게 된다.
예시
구글에서 무엇인가를 검색했을 때, SPA 방식으로 만들어진 웹 사이트에서는 검색 결과가 뜨지 않거나, 검색어와 관련된 정보를 찾기 어렵게 된다.
하지만 검색 엔진이 발전하면서, 이러한 단점을 극복하고 있다. 이제는 SPA 방식으로 만들어진 웹 사이트도 검색 엔진에서 잘 검색되는 경우가 많아졌다.
예를 들어, 페이지 내부의 반복되는 요소들이 있다면, 그것들을 컴포넌트로 추출하고 필요한 곳에서 재사용하면 된다.
모든 컴포넌트를 한 번에 렌더링하면, 성능이 저하될 수 있다.
이런 경우, 화면의 일부분만 렌더링하거나, 필요한 시점에 로드할 수 있는 컴포넌트로 분할하여 성능을 개선할 수 있다.
예를 들어, 웹 사이트에 수백 개의 이미지가 포함되어 있다고 가정해보자.
이러한 이미지를 한 번에 모두 로드하면 웹 페이지가 로드되는 데 매우 오랜 시간이 걸릴 수 있다.
따라서 이미지를 작은 그룹으로 나누어 각 그룹을 필요할 때 로드하도록 할 수 있다. 이렇게 하면 웹 페이지가 빠르게 로드되며, 사용자가 스크롤링하면서 필요한 이미지만 로드된다.
따라서, React에서 컴포넌트를 나누는 것은 코드 재사용성과 애플리케이션 성능 개선을 위해 중요하다.
필요한 시점에 로드되는 컴포넌트로 분할하고, 비슷한 기능을 하는 코드를 추출하여 재사용성을 높이면서, 유지보수와 확장성을 개선할 수 있다.
React Router
라이브러리를 사용한다.React Router
는 라우팅(Routing)을 가능하게 해주는 라이브러리로, 경로에 따라 다른 화면을 보여준다.React Router
라이브러리를 사용하여 라우팅을 구현한다.예를 들어, Twittler와 같은 SPA에서는 메인 트윗 모음 페이지, 알림 페이지, 마이 트윗 페이지 등 다양한 화면이 필요하며, 이러한 화면은 주소에 따라 다르게 보여지게 된다.
React Router
의 주요 컴포넌트에 대해 알아보자. (BrowserRouter
, Routes
와 Route
, Link
)
BrowserRouter
BrowserRouter
는 라우팅을 가능하게 해주는 역할을 한다.
Routes
와Route
Routes
와Route
는 경로를 매칭해주는 역할을 한다.
Link
Link
는 경로를 변경하는 역할을 한다.
npm start
를 통해 실행 결과가 잘 나오는지 확인한다.# simpleroute 폴더에 React App 설치
npx create-react-app@latest simpleroute
cd simpleroute
npm start
React Router
를 설치할 차례이다.# react-router 라이브러리 설치
npm install react-router-dom@^6.3.0
package.json
파일의 dependencies
항목에 react-router-dom
이라는 라이브러리가 등록된 것을 확인할 수 있다.App.js
파일로 가서 최상단에 React Router
라이브러리가 제공하는 컴포넌트들을 사용하기 위한 세팅을 진행한다.import React from 'react'
import { BrowserRouter, Routes, Route, Link } from "react-router-dom"; // 이 구문을 넣어주세요
페이지를 표시하는 컴포넌트 Home
, MyPage
, Dashboard
를 만들어보자.
App.js
하단에 아래와 같이 작성한다.
// Home 컴포넌트
function Home() {
return <h1>Home</h1>;
}
// MyPage 컴포넌트
function MyPage() {
return <h1>MyPage</h1>;
}
// Dashboard 컴포넌트
function Dashboard() {
return <h1>Dashboard</h1>;
}
페이지를 표시하는 컴포넌트를 만들었고, 각 컴포넌트로 이동할 메뉴를 제작한다.
메뉴 제작을 위해 <ul>
요소와 <li>
요소를 이용한다.
function App () {
return (
<div>
<nav>
<ul>
<li>
Home
</li>
<li>
MyPage
</li>
<li>
Dashboard
</li>
</ul>
</nav>
</div>)
}
export default App;
App.js
에 라우팅을 하기 위한 React Router의 주요 컴포넌트를 세팅한다.<BrowserRouter>
컴포넌트는 웹 애플리케이션에서 HTML5의 History API를 사용해 페이지를 새로고침하지 않고도 주소를 변경할 수 있게 해준다.
또한 <BrowserRouter>
가 상위에 작성되어 있어야 React Router의 컴포넌트들을 사용할 수 있다.
function App () {
return (
<BrowserRouter>
<div>
<nav>
<ul>
<li>
Home
</li>
<li>
MyPage
</li>
<li>
Dashboard
</li>
</ul>
</nav>
</div>
</BrowserRouter>)
}
export default App;
아래와 같이 ReactDOM의 렌더 단계인 index.js
에 <BrowserRouter>
를 넣어서 활용할 수도 있다.
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>);
경로를 매칭해주는 역할을 하는 컴포넌트이다.
<Routes>
컴포넌트는 여러 <Route>
컴포넌트를 감싸서 그중 경로가 일치하는 단 하나의 라우터만 렌더링을 시켜주는 역할을 한다.
<Routes>
를 사용하지 않으면 매칭되는 모든 요소를 렌더링한다.
<Route>
컴포넌트는 path
속성을 지정하여 해당 path
에서 어떤 컴포넌트를 보여줄지 정한다.
아래에서 배울 <Link>
컴포넌트가 정해주는 URL 경로와 일치하는 경우에만 작동된다.
이제 <Routes>
컴포넌트와 <Route>
컴포넌트들을 작성해보자.
<Routes>
와 <Route>
로 주소 경로와 아까 만든 3개의 컴포넌트를 연결해 준다.
<Route>
의 path
속성을 이용하여 경로를 작성합니다. 경로와 컴포넌트의 이름은 헷갈리지 않게 동일하게 작성한다.<Route>
태그 안에 element
속성으로 연결하고자 하는 컴포넌트를 넣어준다.function App () {
return (
<BrowserRouter>
<div>
<nav>
<ul>
<li>
Home
</li>
<li>
MyPage
</li>
<li>
Dashboard
</li>
</ul>
</nav>
{/* 주소 경로와 아까 만든 3개의 컴포넌트를 연결해 준다. */}
{/* Routes 컴포넌트는 Route 컴포넌트들을 감싸고 있어야 한다. */}
<Routes>
{/* 경로는 path로 컴포넌트는 element로 연결해준다. */}
<Route path="/" element={<Home />} />
<Route path="/mypage" element={<MyPage />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</div>
</BrowserRouter>)
}
export default App;
팁 : 만약 사용자가 지정된 주소인 “/", “/mypage”, “/dashboard” 이외의 주소로 접근하게 되면 의도한 화면이 보이지 않을 수 있다. 이럴 때 사용할 수 있는 속성이 path=”*” 이다. 지정되지 않은 주소로 접근할 시에는 이 속성이 설정되어 있는 컴포넌트를 보여주게 된다.
경로를 연결해 주는 역할을 하는 컴포넌트이다.
페이지 전환을 통해 페이지를 새로 불러오지 않고 애플리케이션을 그대로 유지하여 HTML5 History API를 이용해 페이지의 주소만 변경해 준다.
ReactDOM으로 렌더를 시키게 되면 <Link>
컴포넌트는 <a>
요소로 바뀌는 모습을 볼 수 있다.
React Router에서
<a>
요소가 아닌<Link>
를 사용하는 이유?
<a>
요소는 페이지를 전환하는 과정에서 페이지를 불러오기 때문에 다시 처음부터 렌더링을 시킨다. 즉, 새로고침 현상이 일어나게 됨.
하지만<Link>
컴포넌트는 페이지 전환을 방지하는 기능이 내장되어 있기 때문에 SPA를 구현할 수 있다.
이어서 <Link>
컴포넌트도 작성해보자.
<Link>
의 to
속성을 활용하여 <Route>
컴포넌트에 설정해 준 path
주소를 연결해 준다.
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;
npm run start
를 통해 실제로 Create React App 프로젝트 환경에서 React Router
를 통해 SPA가 구현되었는지 확인해보자.