[REACT] SPA & Router
1. SSR vs CSR
1-1) SSR (Server Side Rendering)
-클라이언트 요청에 따른 웹 페이지 업데이트 처리를 서버에서 진행한다. 서버는 브라우저로부터 HTTP Request를 받으면 요청한 페이지의 HTML파일 혹은 DB에서 필요한 데이터를 불러와 HTML파일을 업데이트 한 후 브라우저에게 보낸다. 브라우저는 매 요청시마다 새로운 HTML파일을 로드한다.
-검색엔진최적화(SEO)에 유리하지만, 초기 웹페이지 로딩 속도가 느리다.
1-2) CSR (Client Side Rendering)
-서버로부터 받은 HTML 파일에는 기본 골격만 구성되어 있다. 함께 전달된 스크립트 파일을 브라우저가 읽고 페이지를 화면에 렌더링한다. 업데이트 처리를 클라이언트에서 진행한다. 브라우저는 서버로부터 HTTP Response로 HTML파일이 아닌 JSON 형식의 데이터를 받는다. 브라우저의 자바스크립트 엔진은 이를 변환하여 화면에 리렌더링한다. 브라우저는 매 요청시마다 웹 페이지를 리로딩하지 않고 사용자와 상호작용할 수 있다. 이때 사용자가 보고 있는 웹페이지 화면의 HTML은 처음 접속할 때 로드한 HTML파일(빈페이지)과 동일하다.
-사용자와의 빠른 상호작용으로 더 나은 사용자 경험을 제공할 수 있다.
2. SPA(Single Page Application)
-CSR 방식을 지원한다.
-웹사이트가 점점 복잡해지면서 사용자와의 상호작용을 더욱 원활하게 처리하기 위해 도입되었다.
-페이지를 전환할 때마다 모든 컴포넌트를 렌더링하게 되면 서버에 트래픽 과부하로 인한 속도 저하가 발생할 수 있다.
-페이지 전환시 이전 화면과 동일한 컴포넌트(헤더 등)는 렌더링하지 않는다. 업데이트된 데이터만 서버로부터 받는다.
-장점 : 반응이 빠른 사용자 경험을 제공한다.
-단점 : 무거워진 자바스크립트 파일로 인해 첫화면 로딩시간이 길어진다. 그리고 HTML 파일에 데이터가 존재하지 않아 검색엔진에 노출되지 않는다.
-Wireframe : 컴포넌트 구성에 대한 고민. 앱 디자인을 대략적인 윤곽선으로 나타낸 것.
-MockUp : Wireframe에 데모 시연, 테스트 평가 등을 위한 최소한의 기능을 추가한 것.
3. React Router
-React components library
-해당 경로에 라우팅된 컴포넌트만 렌더링한다.
-웹의 History API를 이용하여 방문 기록을 이용할 수 있다. (https://developer.mozilla.org/ko/docs/Web/API/History_API)
-> 앞으로가기/뒤로가기 기능 및 url로 해당 페이지 바로 접근 가능
-리액트 라우터 컴포넌트 :
1) <BrowserRouter>
-리액트 라우터의 최상위 컴포넌트
-HTML5의 History API를 이용하여 페이지를 새로고침하지 않고서도 주소를 변경할 수 있게 해준다.
2) <Routes> & <Route>
-Routes는 여러 Route를 감싸고, 경로가 일치하는 Route만 렌더링 한다.
-Route의 path 속성값에 element 속성값을 매칭한다. -> 어떤 경로에서 어떤 컴포넌트를 렌더링 할 지를 정함
-한 개의 경로에는 하나의 컴포넌트만 라우팅 할 수 있다.
3) <Link> :
-렌더링 후 HTML의 anchor 태그로 변환된다. to 속성은 href 역할을 함
-특별한 점은 anchor와 달리 페이지 전환 과정에서 리로딩이 일어나지 않는다.
-즉, 페이지를 불러 올 때 모든 요소를 렌더링 하지 않고, <Link> 내 요소만 렌더링한다. SPA를 구현하는 핵심 기능이다.
4. React Router 구현하기
3-1) npm install react-router-dom@^6.3.0 을 입력하여 라이브러리를 설치한다.
3-2) package.json 파일에서 dependencies.react-router-dom 을 확인한다.
3-3) JS파일에서 라이브러리를 로드하여 라우터 컴포넌트들을 불러온다.
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
//import는 필요한 모듈을 불러오는 함수다. 구조분해할당과 유사하게 활용함
function App() {
return (
<BrowserRouter>
<div>
<Link to="/">Home</Link>
<Link to="/mypage">MyPage</Link>
<Link to="/dashboard">Dashboard</Link>
// 위 Link 요소를 클릭하면 해당 경로에 따라 아래 <section>의 자식 요소로 Route 컴포넌트가 렌더링된다.
<section>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/mypage" element={<MyPage />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</section>
</div>
</BrowserRouter>
);
}
*path={['/page1', '/page2']}와 같이 작성하면 여러 경로에서 동일한 뷰를 보여줄 수 있다.
3.4) <BrowesrRouter> 컴포넌트는 다음과 같이 렌더링 할 때 적용하는 방법도 있다.
// React Version 17
ReactDOM.render(<BrowserRouter><App/></BrowserRouter>, document.querySelector('#root'));
// React Version 18
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
3.5) <Route path = "*" ~>은 지정하지 않은 주소로 사용자가 접근할 때 보여지는 컴포넌트이다.
3.6) npm run start
4. Nested Routes
-URL의 세그먼트는 컴포넌트의 계층 구조에 대응된다.
-일반적으로 하나의 URL 세그먼트는 하나의 페이지를 렌더링한다. (외부에서 해당 컴포넌트로 url을 통해 바로 접근할 수 있다)
-그리고 URL 구조(레이아웃)에 따라 데이터의 종속성(Dependencies)이 결정된다. (부모를 거쳐야 렌더링이 가능한 컴포넌트가 있다)
4-1) 부모와 자식 라우트를 다른 곳에서 작성
-부모 라우트의 경로에 *를 추가하여 뒤에 오는 모든 경로의 컴포넌트는 이 부모 컴포넌트의 하위요소 임을 나타낸다. (부모 컴포넌트가 항상 함께 렌더링 됨)
-자식 라우트의 경로는 부모 경로를 기반으로 하기 때문에 상대경로를 따른다.
-> path="./경로" 혹은 path="경로" 와 같이 작성한다. 이 경우엔 현재 url 경로에서 이어진다.
-> path="/경로" 는 절대 경로임. 이 경우엔 현재 url을 해당 경로로 변경한다.
-라우트의 index 속성은 index를 가진 자식 라우트가 부모 경로의 기본 컴포넌트임을 나타낸다.
// App.js
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users/*" element={<Users />} />
</Routes>
);
}
// Users.js
function Users() {
return (
<Container>
<Routes>
<Route index element={<Screen />}
<Route path=":id" element={<UserInfo />} />
</Routes>
</Container>
);
}
4-2) 부모와 자식 라우트를 한 곳에 작성
-자식 라우트 정보는 <Outlet />에 전달된다.
-라이브러리에서 <Outlet /> 컴포넌트를 불러와 부모 컴포넌트에서 렌더링 될 위치에 써준다.
// App.js
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users" element={<Users />} >
<Route path=":id" element={<UserInfo />} />
</Routes>
</Routes>
);
}
// Users.js
function Users() {
return (
<Container>
<Outlet />
</Container>
);
}
*잘 정리된 블로그:
https://velog.io/@tjdgus0528/React-Router-v6-%EC%A0%95%EB%A6%AC
https://ui.dev/react-router-nested-routes
5. useNavigate 컴포넌트
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
<button onClick={() => navigate(-1)}>뒤로가기</button>
<button onClick={() => navigate(1)}>앞으로가기</button>
<button onClick={() => navigate('/mypage')}>mypage로 가기</button>
<button onClick={() => navigate('/about')}>about으로 가기</button>
-useNavigate()는 주어진 경로로 이동하는 함수를 반환한다.
-반환된 함수 navigate()는 주소를 인자로 받는데, 두 유형이 존재한다.
1) 1이나 -1을 받으면 History API를 통해 방문 기록을 이용하여 이전 탐색 주소로 이동할 수 있다.
2) 첫번째 인자로 <Link to="~">와 동일한 페이지 주소를 받고, 선택사항으로 두번째 인자인 { replace, state }를 받는다.
*두 번째 인자인 { replace : true }의 경우 History Stack에 있는 기존의 항목에 새 항목을 덮어쓴다. (false는 새 항목을 추가함)
-<a>태그나 <Link> 컴포넌트가 아닌 다른 컴포넌트에 이벤트를 이용하여 페이지 이동 기능을 조건에 따라 추가할 수 있다.
6. 여러 react-router-dom hooks
useParam() : url parameters 정보를 객체로 반환함
useLocation() : 링크된 루트에 전달한 state 값을 반환함
useMatch(str) : str를 현재 컴포넌트의 루트(url)와 비교하여 여러 정보를 반환함. isExact 속성값으로 루트를 판별함