SPA(Single Page Application)
: 페이지가 하나인 어플리케이션
하나의 url로 한 번 페이지가 로딩된 후, 사용자가 다른 페이지로 넘어갈 때(링크 클릭 등) 새로운 페이지가 열리지 않고, 부분내용만 업데이트 되는 싱글페이지 어플리케이션이다.
📌 리액트는 SPA 어플리케이션을 쉽게 만들 수 있도록 도와주는 라이브러리 이다.
전통적인 웹어플리케이션(멀티 페이지 애플리케이션) 구조는 여러 페이지로 구성되어 있었다.
유저 요청시 매번 페이지가 새로고침되고 서버로부터 리소스를 전달받아 해석 후 렌더링을 했으며, 뷰가 어떻게 보여질지도 서버에서 담당했다.
→ 하지만 사용자와 인터랙션이 많은 모던 웹어플리케이션에서는 렌더링하는 것을 서버쪽에서 담당하기에는 서버 자원과 트래픽(서버의 데이터 전송량)이 낭비된다.
→ 따라서 리액트와 같은 라이브러리나 프레임워크를 사용해서 뷰 렌더링을 유저의 브라우저가 담당하도록 하고 우선 어플리케이션을 브라우저에 실행한 다음, 사용자와의 인터랙션이 발생하면 필요한 데이터만 전달받아 보여줄 수 있게 한다.
→ 이렇게 html은 한번만 받아와서 웹 애플리케이션을 실행시킨 후에 그 이후에는 필요한 데이터만 받아와서 화면에 업데이트 해주는 것이 SPA이다.
사용자가 요청한 url에 따라 어떤 특정한 페이지로 연결한다.
1) 브라우저에서 최초에 '/'
경로로 요청을 한다.
2) 서버는 React Web App 을 내려준다.
3) 내려받은 React App 에서 /
경로에 맞는 컴포넌트를 보여준다.
4) React App 에서 다른 페이지로 이동하는 동작을 수행하면
5) 새로운 경로에 맞는 컴포넌트를 보여준다.
❗️ 리액트는 컴포넌트를 작성, 효율적으로 렌더링하는 것에 치중된 프로그램이다. 라우팅 기능은 내장되어 있지 않다.
클라이언트 사이드에서 이루어지는 라우팅을 간단하게 해주며, 가장 대표적인 라우팅 패키지이다.
여러 화면으로 구성된 웹 어플리케이션을 만들게 된다면, react-router 는 필수 라이브러리이다.
✍️ 최근에 버전 5 → 버전 6 으로 큰 버전 업데이트가 되었다고 한다. 새로운 버전으로 차근차근 알아가자.😀
프로젝트에 리액트 라우터를 적용해보자.
$ npm i react-router-dom
$ yarn add react-router-dom
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
Route
컴포넌트를 통해 라우트 설정하기
<Route path="주소규칙" element={보여 줄 컴포넌트 JSX}></Route>
Route
컴포넌트는 Routes
컴포넌트 내부에서 사용되어야 한다.Route
들은 내가 보여줄 각각의 페이지들 이라고 이해하자. const App = () => {
return (
<Routes>
<Route path='/' element={<Home />} />
<Route path='/about' element={<About />} />
</Routes>
);
};
Link
컴포넌트를 통해 다른 주소로 이동시키기
<a href="...">...</a>
태그를 사용하면 안된다.onClick
에 e.preventDefault()
를 호출하고 따로 자바스크립트로 주소를 변환시켜 주는 작업이 필요하다고..)Link 컴포넌트
<a>
태그를 사용하지 않는 이유는 무엇일까 🤔
: <a>
태그의 기본 속성은 페이지를 이동하면서 새로 불러온다는 것이다. 그렇게 되면 리액트 앱이 지닌 상태들도 초기화되고 렌더링된 컴포넌트도 모두 새로 렌더링을 하게 되기 때문!
→ 따라서 Link 컴포넌트를 사용한다.
Link 컴포넌트 역시 a
태그를 사용하긴 하지만, 페이지를 새로 불러오는 것을 막고 HTML5 History API 를 통해 브라우저 주소의 경로만 바꾸는 기능이 내장되어 있다.
import React from 'react';
import {Link} from 'react-router-dom'
const Home = () => {
return (
<div>
<h1>홈</h1>
<p>가장 먼저 보여지는 페이지입니다.🏠</p>
<Link to='/about'>소개</Link>
</div>
);
};
export default Home;
URL Params & Query String
페이지 주소를 정의 할 때, 유동적인 값을 전달하기
/profile/seul
useParams
Hook 을 사용하여 객체 형태로 조회할 수 있다.
URL 파라미터의 이름은 라우트 설정을 할 때 Route 컴포넌트의 path props를 통하여 설정한다.
<Route path='/profile/:username' element={<Profile />}
URL 파라미터는 /profiles/:username
과 같이 경로에 :
를 사용하여 설정한다. 만약 URL 파라미터가 여러개인 경우엔 /profiles/:username/:field
와 같은 형태로 설정할 수 있다.
ex.)
import React from 'react';
import { useParams } from 'react-router-dom';
// 프로필에서 사용할 데이터
const data = {
kim: {
name: 'seul',
description: '코딩은 정말 재밌어 ✨!',
},
lee: {
name: 'nasa',
description: '천문학을 배우면 겸손해져. 🔭',
},
};
const Profile = () => {
const params = useParams();
const profile = data[params.username]
return (
<div>
<h1>사용자 프로필</h1>
{profile ? (
<div>
<h2>{profile.name}</h2>
<p>{profile.description}</p>
</div>
) : (
<p>존재하지 않는 프로필입니다.</p>
)}
</div>
);
};
export default Profile;
/articles?**page=1&keyword=react
?
문자열 이후에 key=value
로 값을 정의하며 &
로 구분을 하는 형태이다. 2-1) useLocation
Hook
location
객체를 반환하며 이 객체는 현재 사용자가 보고있는 페이지의 정보를 지니고 있다.
쿼리스트링은 location.search
값을 통해 조회를 할 수 있다.
ex.)
import { useLocation } from 'react-router-dom';
const About = () => {
const location = useLocation();
console.log(location.search);
return (
<div>
<h1>소개&</h1>
<p>리액트 라우터를 사용해 보는 프로젝트입니다.</p>
<p>쿼리스트링: {location.search}</p>
</div>
);
};
2-2) useSearchParams
Hook
React Router v6부터 useSearchParams
라는 Hook을 통해서 쿼리스트링을 더욱 쉽게 다룰 수 있게 되었다. (쿼리스트링 값을 쉽게 파싱해줌😀 )
배열 타입의 값을 반환한다.
- 첫 번째 원소 : 쿼리파라미터를 조회하거나 수정하는 메서드들이 담긴 객체를 반환.
get
과 set
메서드를 통해 특정 쿼리파라미터를 조회하거나 업데이트할 수 있다. 조회시 쿼리파라미터가 존재하지 않는다면 null
로 조회된다.
- 두 번째 원소 : 쿼리파라미터를 객체형태로 업데이트할 수 있는 함수를 반환.
쿼리파라미터를 조회할 때 값은 무조건 문자열 타입이다.
ex.)
import React from 'react';
import { useSearchParams } from 'react-router-dom';
const About = () => {
const [searchParams, setSearchParams] = useSearchParams();
const detail = searchParams.get('detail');
const mode = searchParams.get('mode');
const onToggleDetail = () => {
// true는 문자열 '' 로 감싸서 값을 비교하기
// mode를 넣지 않으면 onToggleDetail콜백 실행시 mode값이 null로 초기화
setSearchParams({ detail: detail === 'true' ? false : true, mode });
};
const onIncreaseMode = () => {
// 숫자는 parseInt로 숫자 타입으로 변환하기
// detail 넣지 않으면 onIncreaseMode콜백 실행시 detail값이 null로 초기화
const nextMode = mode === 'null' ? 1 : parseInt(mode) + 1;
setSearchParams({ mode: nextMode, detail });
};
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해 보는 프로젝트 입니다.😎</p>
<p>detail: {detail}</p>
<p>mode: {mode}</p>
<button onClick={onToggleDetail}>Toggle detail</button>
<button onClick={onIncreaseMode}>mode + 1</button>
</div>
);
};
export default About;
라우트 내부의 라우트 만들기
컴포넌트를 만들어서 그 안에 또 Route 컴포넌트를 렌더링한다.
Outlet 컴포넌트
리액트 라우터에서 제공하는 Outlet
컴포넌트는 Route
의 children
으로 들어가는 JSX 엘리먼트를 보여주는 역할을 한다.
중첩된 라우트와 Outlet
은 페이지끼리 공통적으로 보여지는 레이아웃을 유용하게 사용할 수도 있다. (ex.header 컴포넌트 등) 이때, 중첩된 라우트 방식을 사용하면 컴포넌트를 한 번만 사용해도 된다는 장점이 있다.
ex.)
// App.js
const App = () => {
return (
<Routes>
<Route element={<Layout />}>
<Route index element={<Home />} />
<Route path='/about' element={<About />} />
<Route path='/profile/:username' element={<Profile />} />
</Route>
<Route path='/articles/' element={<Articles />}>
<Route path=':id' element={<Article />} />
</Route>
</Routes>
);
};
// articles.jsx
import { Link, Outlet } from 'react-router-dom';
const Articles = () => {
return (
<div>
<Outlet />
<ul>
<li>
<Link to='/articles/1'> 게시글 1 </Link>
</li>
<li>
<Link to='/articles/2'> 게시글 2 </Link>
</li>
<li>
<Link to='/articles/3'> 게시글 3 </Link>
</li>
</ul>
</div>
);
};
export default Articles;
// article.jsx
import { useParams } from 'react-router-dom';
const Article = () => {
const { id } = useParams();
return (
<div>
<h2>게시글 {id}</h2>
</div>
);
};
export default Article;
// layout.jsx
import { Outlet } from 'react-router-dom';
const Layout = () => {
return (
<div>
<header style={{ background: 'lightgray', padding: 16, fontSize: 24 }}>
Header
</header>
<main>
<Outlet />
</main>
</div>
);
};
export default Layout;
Route
컴포넌트 안의index
props
path='/'
와 동일한 역할을 하며 더 명시적으로 표현하는 방법이다.ex.)
<Route index element={<Home />} />
라우팅에 관련된 작업을 할 때 유용하게 사용되는 API들
Link
가 특정 주소로 이동해주는 태그라면,Navigate
는 특정 행동을 했을 때 해당 주소로 이동해준다.
navigate("../success", { replace: true });
{replace, state}
인수 → replace(ture or false)Link
컴포넌트를 사용하지 않고 다른 페이지로 이동을 해야 하는 상황에 사용한다.navigate
함수를 사용할 때 파라미터가 숫자 타입이라면 앞으로 가거나, 뒤로 간다. replace
옵션을 사용하면 현재 페이지를 기록에 남기지 않는다. (뒤로가기 시 건너 뜀)useNavigate()
는 react v6에서 useHistory가 변화한 것이다. 이 때 useHistory
에서 사용하던 window의 history 를 이용한 navigate 기능도 할 수 있다고 한다.ex.)
const Layout = () => {
const navigate = useNavigate();
const goBack = () => {
// 이전 페이지로 이동
navigate(-1);
};
const goArticles = () => {
// 현재 페이지를 페이지 기록에 남기지 않음
navigate('/articles', { replace: true });
};
return (
<div>
<header style={{ background: 'lightgray', padding: 16, fontSize: 24 }}>
<button onClick={goBack}> 뒤로가기 </button>
<button onClick={goArticles}> 게시글 목록 </button>
</header>
<main>
<Outlet />
</main>
</div>
);
};
링크에서 사용하는 경로가 현재 라우트의 경로와 일치하는 경우 특정 스타일 또는 CSS 클래스를 적용한다.
style
또는 className
을 설정할 때 { isActive: boolean }
을 파라미터로 전달받는 함수 타입의 값을 전달한다. ex.)
const activeStyle = {
color: 'pink',
fontSize: 20,
};
<li>
<NavLink
to='/articles/1'
style={({ isActive }) => (isActive ? activeStyle : undefined)}
>
게시글1
</NavLink>
</li>
페이지를 찾을 수 없을 때 나타나는 페이지
<Route path='*' element={<NotFound />} />
*
는 wildcard 문자로, 아무 텍스트나 매칭한다는 뜻이다. 즉, 이 라우트는 엘리먼트 상단에 위치하는 라우트들의 규칙을 모두 확인하고, 일치하는 라우트가 없다면 이 라우트<NotFound />
를 화면에 보여준다.페이지를 리다이렉트 할 때 사용한다.
reference)
vlpt-react-router
dreamcoding
fastcampus
useNavigate