한개의 페이지로 이루어진 어플리케이션,
서버로부터 완전한 새로운 페이지를 불러오지 않고 페이지 갱신에 필요한 데이터만 받아 현재의 페이지를 업데이트하는 어플리케이션.
- 필요한 부분만 업데이트 하므로 사용자와의 interaction 이 빠르다.
- 서버에서는 요청 받은 데이터만 넘겨주므로 서버 과부하 문제가 줄어든다.
- 전체 페이지 렌더링이 아니기에 더 나은 UX(유저경험) 제공
앱의 규모가 커지면 Javascript 파일의 크기가 크기 때문에 첫 화면 로딩 시간이 길어진다.
(그 이유는 첫 화면 로딩 시 읽어들인 HTML 파일은 거의 비어있고, 대부분의 코드는 JavaScript 파일 안에 들어있다 보니 자연스럽게 JavaScript 파일이 무거워지고 때문에 이 JavaScript 파일을 기다리는 시간으로 인해 첫 화면의 로딩 시간이 길어진다.
또한 사용자가 실제로 방문하지 않는 페이지의 스크립트도 불러오기 때문이다.)
--> 이 문제는 추후에 코드 스플리팅을 사용하여 라우트별로 파일들을 나누어 해결 할 것임.
- 검색 엔진 최적화(SEO)가 좋지 않습니다. 구글이나 네이버 같은 검색엔진이 자료를 수집하기 좋도록 웹 페이지를 구성하는 것을 뜻함.
검색 엔진의 작동 방식은 검색 로봇이 웹 페이지에 있는 정보를 수집하고 분석해서 그 결괏값에 인덱스를 만들어 보관하고 있다가 사용자가 검색어를 입력하면 보관하고 있던 인덱스에서 검색어와 가장 연관성이 높은 웹 페이지들을 순서대로 보여주는 방식으로 작동하기에 SPA의 경우 HTML파일은 별다른 자료가 없기 때문에 검색엔진이 적절히 작동하지 않는다.
--> 이 문제는 서버사이드 렌더링 (SSR)로 해결할 수 있다.
SPA는 하나의 페이지를 가지고 있지만 사실 한 종류의 화면만 사용하지 않는다.
라우팅(Routing) : 다른 주소에 따라 다른 뷰를 보여주는 과정을 경로에 따라 변경한다.
React SPA에서는 라우팅을 위해 React Router라는 라이브러리를 가장 많이 사용한다.
import { BrowserRouter, Routes, Route, Link, NavLink } from "react-router-dom";
위와 같이 불어와서 사용할 수 있다.
BrowserRouter : 라우터 역할
Routes 와 Route : 경로를 매칭
Link : 경로를 변경하는 역할
NavLink : 공식문서에선 NavLink 컴포넌트는 Link의 special version 이라고 명시 되어 있다.
특정 링크에 스타일을 넣어 줄 수 있다.
자체적으로 isActive라는 boolean값을 가지고 있다.
NavLink 태그안에 isActive를 선언하여 활성화시키고 싶은 스타일에 css를 적용 할 수 있다.
(클릭/활성화 시 active class 변경됨)
//예시
const Category = styled(NavLink)`
cursor: pointer;
text-decoration: none;
color: inherit;
padding-bottom: 0.25rem;
&:hover {
color: #495057;
display: flex;
}
&.active {
font-weight: 600;
border-bottom: 2px solid #22b8cf;
color: #22b8cf;
&:hover {
color: #3bc9db;
}
}
`;
// ...
const Categories = () => {
return (
<CategoriesBlock>
{categories.map((c) => (
<Category
key={c.name}
{/* 아래와 같이 특정 스타일을 줄 수 있다. */}
className={({ isActive }) => (isActive ? 'active' : '')}
to={c.name === 'all' ? '/' : `/${c.name}`}
>
{c.text}
</Category>
))}
</CategoriesBlock>
);
};
useParams란?
useParams는 리액트에서 제공하는 Hook으로 동적으로 라우팅을 생성하기 위해 사용한다.
- URL에 포함되어있는 Key, Value 형식의 객체를 반환해주는 역할
- Route 부분에서 Key를 지정해주고, 해당하는 Key에 적합한 Value를 넣어 URL을 변경시키면,
useParams를 통해 Key, Value 객체를 반환받아 확인할 수 있다.
반환받은 Value를 통해 게시글을 불러오거나, 검색목록을 변경시키는 등 다양한 기능으로 확장시켜 사용할 수 있다.
내가 사용해본 예제
//App.js
import './App.css';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import NewsPage from './pages/NewsPage';
const App = () => {
return (
<>
<BrowserRouter>
<Routes>
{/* ?를 붙이면 category 값이 선택적이라는 의미다,
있을수도 없을 수도 있다는 것이다. */}
<Route path="/:category?" element={<NewsPage />}></Route>
</Routes>
</BrowserRouter>
</>
);
};
export default App;
// NewsPage.js
import React from 'react';
import Categories from '../components/Categories';
import NewsList from '../components/NewsList';
//import해오기
import { useParams } from 'react-router-dom';
const NewsPage = () => {
//Route의 path와 이름을 맞춰줘야 한다.
const { category } = useParams();
return (
<>
<Categories />
{category === undefined ? <NewsList category={'all'} /> : <NewsList category={category} />}
</>
);
};
export default NewsPage;
위와 같이 category를 params 로 받아와 아래와 같이 비동기적 처리를 할 수 있다.
// 받아온 category로 비동기 처리를 하는 경우
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const query = category === 'all' ? '' : `&category=${category}`;
const response = await axios.get(`https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=${API_KEY}`);
setArticles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false);
};
fetchData();
}, [category]);
간단한 예제도 살펴보면 이해가 쉽다.
import React from 'react'
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function App() {
return (
<BrowserRouter> //ReactDOM의 렌더 단계인 index.js 에 <BrowserRouter>를 넣어서 사용가능
<div>
<nav>
<ul>
<li>
{/*<Link> 의 to 속성을 활용하여 <Route> 컴포넌트에 설정해 준 path 주소를 연결*/}
{/*ReactDOM으로 렌더를 시키게 되면 <Link> 컴포넌트는 <a> 요소로 바뀐다.
하지만 <a> 요소는 페이지를 전환하는 과정에서 페이지를 불러오기 때문에 다시 처음부터 렌더링을 시킨다.
즉, 새로고침 된다.
<Link> 컴포넌트는 페이지 전환을 방지하는 기능이 내장되어 있기 때문에 SPA를 구현할 수 있다.*/}
<Link to="/">Home</Link>{/* Link 컴포넌트를 이용하여 경로를 연결합니다 */}
</li>
<li>
<Link to="/mypage">MyPage</Link>
</li>
<li>
<Link to="/dashboard">Dashboard</Link>
</li>
</ul>
</nav>
{/*<Routes> 컴포넌트는 여러 <Route> 컴포넌트를 감싸서 그중 경로가 일치하는 단 하나의 라우터만 렌더링을 시켜주는 역할*/}
<Routes>
{/* 경로는 path로 컴포넌트는 element로 연결해 줍니다. */}
{/*<Link> 컴포넌트가 정해주는 URL 경로와 일치하는 경우에만 작동*/}
{/* path=”*” 사용시 지정되지 않은 주소로 접근할 시에 이 속성이 설정되어 있는 컴포넌트를 보여준다*/}
<Route path="/" element={<Home />} />
<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;
만약 index.js에서 <BrowserRouter>
를 넣어서 활용시
//(React Version 17 )
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(<BrowserRouter><App/></BrowserRouter>, document.querySelector('#root'));
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
//React Version 18
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
URL 파라미터와 쿼리
추가적으로 페이지 주소를 정의할 때 가끔 유동적인 값을 전달해야 한다.
- 파라미터 : /profile/veloper
- 쿼리 : /about?details=true
유동적인 값을 사용해야 하는 상황에서는 일반적으로
파라미터는 특정 아이디 또는 이름을 사용하여 조회할 때 사용하고,
쿼리는 어떤 키워드를 검색하거나 페이지에 필요한 옵션을 전달할 때 사용한다.
추가적인 기능들
import { useNavigate } from 'react-router-dom';
function Func() {
const navigate = useNavigate();
const onClickImg = () => {
navigate(`/comment/id/등등 내가 원하는 주소`);
};
return (
<img src={...} alt={...} onClick={onClickImg} />
);
}
export default Func;
구조 : navigate('/about', {replace: true});
navigate는 첫번째 인자로는 url, 두번째 인자로는 {replace} 인수를 받는다.
- replace() 값으로 true를 사용하면 navigate에 적힌 주소로 넘어간 후 뒤로가기를 하더라도 종전 페이지로 돌아오지 않고 메인 페이지(’/’) 로 이동한다.
- default값은 false로 뒤로가기가 가능.
- useNavigate는 react-router-dom V6에서 새로 생긴 함수로 기존의 useHistory기능을 전부 대체할 수 있다.
- useHistory의 history는 객체, useNavigate의 navigate는 함수이다!
// v6 const navigate = useNavigate(); navigate('/home'); navigate('/home', {replace: true}); navigate(-1); // 뒤로가기 navigate(1); // 앞으로 가기
추가적인 history 나 match 객체와 같은 내용을 담아 수정해야겠다.