Single Page Application의 약어, 한 개의 페이지로 이루어진 어플리케이션이라는 뜻입니다.
전통적인 웹 페이지는 다음과 같이 구성되어 있었습니다.
사용자가 다른 페이지로 이동할 때마다 새로운 html을 받아오고, 페이지를 로딩할 때마다 서버에서 리소스를 전달받아 해석한 뒤 화면에 보여줍니다. 이후 사용자에게 보이는 화면을 서버 측에서 준비하고, 사전에 html 파일을 만들어 제공하거나, 데이터에 따라 유동적인 html을 생성해 주는 템플릿 엔진을 사용합니다.
그러나 요즘은 웹에서 제공하는 정보가 많이 때문에, 서버 측에서 모든 뷰를 준비하면 성능상의 문제가 발생할 수 있습니다. 또한 html을 서버에 요청할 때, 인터페이스에서 사용하고 있던 상태를 유지하는 것도 번거롭고, 바뀌지 않는 부분까지 새로 불러와 보여 주어야 하기 때문에 불필요한 로딩이 있어 비효율적입니다.
그래서 리액트와 같은 라이브러리, 혹은 프레임워크를 사용하여 뷰 렌더링을 브라우저가 담당하게 했습니다.
어플리케이션을 브라우저에 불러와서 실행한 뒤, 사용자와의 인터랙션이 발생하면 필요한 부분만 자바스크립트를 사용하여 업데이트합니다. 새로운 데이터가 필요하면 서버 API를 호출하여 필요한 데이터만 새로 불러와 애플리케이션에서 사용할 수도 있습니다.
SPA는 서버에서 사용자에게 제공하는 페이지는 한 종류이지만, 해당 페이지에서 로딩된 자바스크립트와 주소 상태에 따라 다양한 화면을 보여줄 수 있는데, 이때 사용하는 것이 라우팅입니다.
리액트 라우팅 라이브러리의 종류는 다음과 같습니다. 언급된 것 외에도 다양한 라우팅 라이브러리가 존재합니다.
리액트 라우터는 다음과 같은 특징을 갖고 있습니다.
클라이언트 사이드에서 이루어지는 라우팅을 아주 간단하게 구현할 수 있도록 합니다.
이후 서버 사이드 렌더링을 할 때, 라우팅을 도와주는 컴포넌트들을 제공해 줍니다.
1. 앱의 규모가 커지면 자바스크립트 파일이 너무 커진다는 점, 페이지 로딩 시 사용자가 실제로 방문하지 않을 수도 있는 페이지의 스크립트도 불러온다는 점
코드 스플리팅을 사용해 라우트별로 파일들을 나누어서 트래픽과 로딩 속도를 개선할 수 있습니다.
2. 브라우저에서 자바스크립트를 사용하여 라우팅을 관리할 경우, 자바스크립트를 실행하지 않는 일반 크롤러에서는 페이지의 정보를 제대로 수집하지 못한다는 잠재적인 단점
3. 자바스크립트가 실행될 때까지 페이지가 비어있기 때문에, 자바스크립트 파일이 로딩되어 실행되는 짧은 시간 동안 흰 페이지가 나타날 수 있다는 점
2,3 모두 이후 배울 서버 사이드 렌더링 (sercer-side rendering)을 통해 해결할 수 있습니다.
$ yarn create react-app router-tutorial
$ cd router-tutorial
$ yarn add react-router-dom
index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
프로젝트에 리액트 라우터를 적용할 때는 index.js 파일에서 react-router-dom에 내장되어 있는 BrowserRouter라는 컴포넌트를 사용하여 감싸 주어야 합니다.
Home.js
import React from "react";
const Home = () => {
return (
<div>
<h1>홈</h1>
<p>홈, 그 페이지는 가장 먼저 보여지는 페이지.</p>
</div>
);
};
export default Home;
About.js
import React from "react";
const About = () => {
return (
<div>
<h1>소개</h1>
<p>이 프로젝트는 리액트 라우터 기초를 실습해 보는 예제 프로젝트입니다.</p>
</div>
);
};
export default About;
라우트로 사용할 페이지 컴포넌트를 만들었습니다.
본격적으로 컴포넌트를 연결하기에 앞서, 책에 작성된 내용은 업그레이드 전 버전인 v5 버전 기준으로 작성되어 있어, 공식 문서를 바탕으로 코드를 변환하여 실습을 진행했습니다.
V5와 V6에서 차이가 나는 부분에 대해서는 인용으로 표시해 두었습니다.
[공식 문서] V6과 V5의 차이점 https://reactrouter.com/docs/en/v6/upgrading/v5
V5과 V6, 그 차이점
V5에서 달라진 점은 component가 element로 변경되었다는 점입니다.
V6에서 Route 컴포넌트를 사용하는 규칙은 다음과 같습니다.
<Route path="주소규칙" element={<보여 줄 컴포넌트 />} />
App.js
import React from "react";
import { Route, Routes } from "react-router-dom";
import About from "./About";
import Home from "./Home";
const App = () => {
return (
<div>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</div>
);
};
export default App;
V5과 V6, 그 차이점
기존 V5에서는 Route를 각각 작성해 주어도 문제가 없었습니다.
단, V6에서는 Route들을 Routes로 감싸주지 않으면 오류가 발생합니다.
이때, App.js에서는 Home과 About. 두 컴포넌트가 모두 나타납니다.
이를 방지해 주기 위하여, exact라는 props를 true로 설정해 주어야 합니다.
App.js
import React from "react";
import { Route, Routes } from "react-router-dom";
import About from "./About";
import Home from "./Home";
const App = () => {
return (
<div>
<Routes>
<Route path="/" element={<Home />} exact={true} />
<Route path="/about" element={<About />} />
</Routes>
</div>
);
};
export default App;
Link 컴포넌트는 클릭하면 다른 주소로 이동시켜 주는 컴포넌트입니다. 일반 웹 어플리케이션에서는 a 태그에 해당하는 기능입니다.
Link 컴포넌트는 다음과 같이 사용합니다. 이는 v5와 v6 버전에서 모두 동일하게 적용합니다.
<Link to="주소">내용</Link>
위를 바탕으로 App.js에 Link 컴포넌트를 만들어 보겠습니다.
App.js
import React from "react";
import { Route, Routes, Link } from "react-router-dom";
import About from "./About";
import Home from "./Home";
import Profile from './Profile';
const App = () => {
return (
<div>
<ul>
<li>
<Link to="/">홈</Link>
</li>
<li>
<Link to="/about">소개</Link>
</li>
</ul>
<hr />
<Routes>
<Route path="/" element={<Home />} exact={true}/>
<Route path="/about" element={<About />}/>
</Routes>
</div>
);
};
export default App;
App.js
import React from "react";
import { Route, Routes, Link } from "react-router-dom";
import About from "./About";
import Home from "./Home";
import Profile from './Profile';
const App = () => {
return (
<div>
<ul>
<li>
<Link to="/">홈</Link>
</li>
<li>
<Link to="/about">소개</Link>
</li>
</ul>
<hr />
<Routes>
<Route path="/" element={<Home />} exact={true}/>
<Route path="/about" element={<About />}/>
<Route path="/info" element={<About />}/>
</Routes>
</div>
);
};
export default App;
다음과 같이 Route를 두 번 사용하여 Route 하나에 여러 개의 path를 설정할 수 있습니다.
V5과 V6, 그 차이점
V5에서는 <Route path={['/about', '/info']} ~> 등, path를 props 배열로 설정해 줄 수 있었지만,
V6으로 업데이트가 된 후에는 path의 경로가 문자열이어야만 합니다.
즉, path를 props 배열로 사용하였던 기존 방식은 사용하지 못하게 되었습니다.
페이지 주소를 정의할 때 유동적인 값을 전달해야 할 경우 두 가지로 나눌 수 있습니다. 파라미터와 쿼리입니다.
일반적으로 파라미터는 특정 아이디 혹은 이름을 사용하여 조회할 때 사용하며, 쿼리는 어떤 키워드를 검색하거나 페이지에 필요한 옵션을 전달할 때 사용합니다.
/profile/사용자명과 같은 형식으로 뒷부분에 유동적인 username 값을 넣어 줄 때, 해당 값을 props로 받아 와 조회하는 방법에 대하여 알아보겠습니다.
Profile.js
import React from "react";
import { useParams } from 'react-router-dom';
const data = {
velopert: {
name: "김민준",
description: "리액트를 좋아하는 개발자",
},
gildong: {
name: "홍길동",
description: "고전 소설 홍길동전의 주인공",
},
};
const Profile = () => {
const { username } = useParams();
/* V5 ver: const Profile = ({ match }) => {
const { username } = match.params; */
const profile = data[username];
if (!profile) {
return <div>존재하지 않는 사용자입니다.</div>;
}
return (
<div>
<h3>
{username}({profile.name})
</h3>
<p>{profile.description}</p>
</div>
);
};
export default Profile;
V5과 V6, 그 차이점
V5에서는 파라미터를 받기 위해서 match 객체를 사용했는데,
V6에서는 useParams를 사용해야 합니다.
app.js
import React from "react";
import { Route, Routes, Link } from "react-router-dom";
import About from "./About";
import Home from "./Home";
import Profile from './Profile';
const 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>
<hr />
<Routes>
<Route path="/" element={<Home />} exact={true}/>
<Route path="/about" element={<About />}/>
<Route path="/profile/:username/*" element={<Profile />}/>
// v6 버전에서 변동됨
// v5: <Route path="/profile/:username" component={Profile} />
</Routes>
</div>
);
};
export default App;
V5와 V6, 그 차이점
V5에서는 username 값을 조회하기 위해 /:username 만 사용해도 되었지만,
V6에서는 username 값을 조회하기 위해서는 /:username 뒤에 /*를 붙여 주어야 합니다. (ex: ../:username/*)
이번에는 About 페이지에서 쿼리를 받아 오겠습니다.
About.js
###V5###
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;
###V6###
import React, { useMemo } from "react";
import { useLocation } from "react-router";
function useQuery(){
const {search} = useLocation();
return useMemo(()=> new URLSearchParams(search), [search])
}
const About = () => {
let query = useQuery();
const showDetail = query.get('detail') ==='true'
return (
<div>
<h1>소개</h1>
<p>이 프로젝트는 리액트 라우터 기초를 실습해 보는 예제 프로젝트입니다.</p>
{showDetail && <p>detail 값을 true로 설정하셨군요!</p>}
</div>
);
};
export default About;
위와 같이 코드를 작성할 경우, ../about?detail=true 으로 접속하였을 때 detail 값을 true로 설정하셨군요! 라는 문구가 나타나는 것을 볼 수 있습니다.
V5와 V6, 그 차이점
V5에서는 라우트 컴포넌트에게 전달되는 location 객체에 있는 search 값에서 Query를 읽을 수 있었고,
문자열을 객체 형태로 변환하기 위하여 qs라는 라이브러리를 사용했습니다.
V6부터는 location 객체가 사라진 듯하며, useLocation을 사용해야 합니다.
서브 라우트는 라우트 내부에 또 라우트를 정의하는 것을 의미합니다.
방법은 간단합니다. 라우트로 사용되고 있는 컴포넌트의 내부에 Route 컴포넌트를 또 사용해 주면 됩니다.
Profiles.js
###V5###
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;
###V6###
import React from 'react';
import { Link, Route, Routes } 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>
<Routes>
<Route path="/*" element={<div>유저를 선택해주세요.</div>} />
<Route path=":username" element={<Profile />} />
</Routes>
</div>
);
};
export default Profiles;
V5와 V6, 그 차이점
V5에서는 <Profiles /> 컴포넌트를 렌더링한 뒤, Profiles 주소로 들어오면 render 안의 <div>를 렌더링하고, <Profiles /> 컴포넌트를 렌더링해 주었습니다.
V6에서 변동된 점은 다음과 같습니다.
- render => element, 화살표 함수 사용하지 않음.
- 하위 페이지가 있을 경우, 부모 Route에 /*을 추가해 주어야 합니다. 이는 exact의 역할을 대신합니다.
- path에서 부모 경로는 필요없으며, 파라미터만 작성해 줍니다. (:username)
- 앞서 언급했듯, <Route>들을 <Routes>로 싹 다 감싸줘야 합니다.
대부분의 기능이 V6으로 업그레이드되며 사라지거나, 변동되었습니다.
모든 문단이 변동되었다고 판단하여 앞서 이야기했던 인용 표시는 생략하였습니다.
useNavigate로 통일되었습니다.
// history 사용을 원할 경우
import { createBrowserHistory} from 'react-router-dom';
const history = createBrowserHistory();
// useNavigate을 사용하는 방법
import { useNavigate } from 'react-router-dom';
function NavigateSample() {
const navigate = useNavigate();
// 뒤로가기
const goBack = () => {
navigate(-1); //원하는 횟수만큼 숫자를 변경해 줍니다.
}
// 홈으로 가기
const goHome = () => {
navigate('/');
}
return ( ... );
}
페이지에서 떠날 것인지를 묻는 Prompt를 띄우기 위해서는 다음 참고 문서와 같이 코드를 작성하면 됩니다.
withRouter, useRouteMatch, match가 사라졌습니다. 이와 관련된 공식 문서를 하단에 첨부하였습니다.
https://reactrouter.com/docs/en/v6/faq#what-happened-to-withrouter-i-need-it
Switch 컴포넌트는 Route를 감싸서 일치하는 라우트를 렌더링해 주었습니다.
V6부터 Routes로 변동되며 Not Found 페이지 구현 방식이 변동되었습니다.
<Routes>
<Route path="/" element={<home />} />
<Route path="/*" element={<h1>존재하지 않는 페이지입니다.</h1>} />
</Routes>
NavLink는 현재 경로와 Link에서 사용하는 경로가 일치하는 경우, 특정 스타일 혹은 클래스를 적용할 수 있는 컴포넌트입니다.
V5에서는 링크가 활성화되었을 때의 스타일을 activeStyle, CSS 클래스를 적용할 때는 activeClassName을 사용해 주었습니다.
그러나 V6으로 업그레이드되며 activeStyle과 activeClassName이 모두 사라졌고, 이제는 isActive로 사용해 주어야 합니다.
### V5 ###
(...)
const Profiles = () => {
const activeStyle = {
background: "black",
color: "white",
};
return (
<div>
<h3>사용자 목록:</h3>
<ul>
<li>
<NavLink activeStyle={activeStyle} to="/profiles/velopert" active>
velopert
</NavLink>
</li>
<li>
<NavLink activeStyle={activeStyle} to="/profiles/gildong">
gildong
</NavLink>
</li>
</ul>
(...)
</div>
);
};
export default Profiles;
### V6 ###
(...)
const Profiles = () => {
return (
<div>
<h3>사용자 목록:</h3>
<ul>
<li>
<NavLink to="./velopert" style={({ isActive }) => ({ color: isActive ? 'green' : 'blue' })}>
velopert
</NavLink>
</li>
<li>
<NavLink to="./gildong" style={({ isActive }) => ({ color: isActive ? 'green' : 'blue' })}>
gildong
</NavLink>
</li>
</ul>
(...)
</div>
);
};
export default Profiles;
달라진 부분만 더 자세히 살펴보도록 하겠습니다.
###V5###
<NavLink activeStyle={activeStyle} to="/profiles/velopert" active>
velopert
</NavLink>
###V6###
<NavLink to="./velopert" style={({ isActive }) => ({ color: isActive ? 'green' : 'blue' })}>
velopert
</NavLink>
activeStyle로 스타일이 활성화되었는지 확인했던 V5와 달리, V6에서는 활성화가 되었는지 확인한 뒤, 색상을 변경해 줍니다.
V5에서는 스타일을 따로 지정해 주었던 것과 달리 V6에서는 스타일을 화살표 함수로 만들어 줍니다.
위와 같이 코드를 작성할 경우 정상적으로 출력됨을 확인할 수 있습니다.
1. 리액트 라우터를 적용할 때는 react-router-dom에 내장되어 있는 ( )라는 컴포넌트를 사용하여 감싸 주어야 합니다.
2. Route 컴포넌트로 특정 주소에 컴포넌트를 연결하는 방법을 적으시오.
3. V6에서는 Route들을 ( )로 감싸 주어야 합니다.
4. 일반적으로 ( )는 특정 아이디 혹은 이름을 사용하여 조회할 때 사용하며, ( )는 어떤 키워드를 검색하거나 페이지에 필요한 옵션을 전달할 때 사용합니다.
1. BrowserRouter
2. <Route path="주소규칙" element={<보여 줄 컴포넌트 />} />
3. Routes
4. 파라미터, 쿼리
Corner React Starter #1
Editor PeeP
[리액트를 다루는 기술] 17장 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 (0) | 2022.01.31 |
---|---|
[리액트를 다루는 기술] 16장 리덕스 라이브러리 이해하기 (0) | 2022.01.31 |
[리액트를 다루는 기술] 11장 컴포넌트 성능 최적화 (0) | 2022.01.17 |
[리액트를 다루는 기술] 12장 immer를 사용하여 더 쉽게 불변성 유지하기 (0) | 2022.01.17 |
[리액트를 다루는 기술] react 프로젝트 Netlify에서 배포하기 (0) | 2022.01.10 |