11월 3일 react-router v6 가 릴리즈 되었습니다. 그동안 당연하게 사용하고있던 라우터에 대해서 어떤방식으로 동작하는지 기존에 어떤 문제들을 해결하고자 하였는지 알아보려고합니다.
용어정의
라우터란?
라우터(라우팅 기능을 갖는 공유기)는 컴퓨터 네트워크간에 데이터 패킷을 전송하는 네트워크장치이다.
패킷의 위치를 추출하여 그 위피에 대한 최적의 경로를 지정하며, 이 경로를 따라 데이터 패킷을 다음 장치로 전달한다.
- 위키백과 -
라우터라고 하면 일반적으로 네트워크 장치를 말합니다. 중계기라고도 볼 수 있으며 서로 다른 네트워크간에 경로를 전달하는 역할을 담당하고 있습니다. 리액트에서 사용하고 있는 라우터또한 물리적인 중계기는 아니지만 각각의 컴포넌트 페이지 간에 경로전달을 담당하고 있습니다.
라우팅
라우팅은 어떤 네트워크 안에서 통신 데이터를 보낼 때 최적의 경로를 선택하는 과정이다.
최적의 경로는 주어진 데이터를 가장 짧은 거리로 또는 가장 작은 시간 안에 전송할 수 있는 경로이다.
라우팅은 전화 통신망, 전자 정보 통신망, 그리고 교통망 등 여러 종류의 네트워크에서 사용된다.
용어에 대해서 정의하기위해 찾아보던 도중 스위칭 이란 용어가 쓰이는걸 봤습니다. 아마 리액트 라우트에서 사용하던 Swich 도 여기서 따와서 사용하고 있었던것같습니다. 현재 v6 에서는 Routes 로 이름이 변경되었다고 합니다.
리액트 라우터 v6 공식문서 내용중 주요개념에 작성된 라우터에 대한 설명은 아래와같이 작성되어 있습니다.
- Router : 상태 저장 최상위 구성요소 입니다.
- Route config : 경로 일치의 분기를 생성하기 위해 현재 위치에 대해 순위를 매기고 일치할 경로 객체의 트리입니다.
- Route : 일반적으로
{ path, element }또는 모양을 가진 개체 또는 경로 요소 입니다. path 패턴이 현재 URL과 일치하면 element 가 랜더링 됩니다.
오래된 방식 알아보기
리액트의 라우터에 대해서 알아보기전에 이전에 페이지 전환은 어떤 방식으로 했었는지 알아볼 필요가 있을것같습니다. 결국 기술의 등장 배경은 기존방식의 불편함과 비효율성을 개선하기위해 등장한것이기 때문에 이전 방식을 사용하지 않더라도 지금의 기술을 이해하는데 바탕이 될 수 있습니다.
기존에 페이지 전환은 html 의 <a> 태그를 활용하는 방식 이었습니다.
그리고 이렇게 페이지가 늘어날때마다 html 파일 갯수또한 늘어나는 방식이었습니다. 이 페이지들은 모두 정적 페이지 였으며 동적인 페이지를 생성하여 만들기 위해서는 JSP 나 PHP 를 활용하여 SSR 방식으로 페이지를 그려주었습니다.
단점
- 페이지가 늘어나는 만큼 html 정적 파일도 늘어남
- 중복되는 UI 에 대해서 재사용 불가
- 페이지 이동시마다 랜더링 비용 발생
- 전통적인 SSR 방식으로 커버가 가능하지만 서버코드와 UI 코드가 분리되지않음
- 유지보수와 가독성이 좋지않음
- 서버에서 그려줄경우 사용자가 실제 화면을 보기까지 지연 로딩 발생
기존 방식은 점점더 요구사항이 많아지고 복잡도를 더해가는 웹서비스를 만들기에는 적합하지 않았고 코드를 작성할때도 마찬가지이지만 복잡도가 더해질경우 우리는 의존성과 응집도의 분리를 시도합니다. 작게 쪼개는만큼 관리와 가독성면에서는 좋아지니까요.
리액트로 알아보는 라우터
리액트는 이런 문제들을 해결하기위해 등장했다고 볼 수 있습니다. 하나의 페이지만 사용하는 SPA 방식을 사용함으로써 사용자에게 모바일과 유사한 경험을 제공하고 관심사를 분리시킴으로써 더 생산적인 작업이 가능하게 됩니다. 다만 React 는 UI 라이브러리이기때문에 기본적으로 라우터같은 기능을 제공하지는 않습니다.
때문에 서드파티 라이브러리를 사용해야하는데 그 중 페이지 처리를 위해 가장 많이 사용되는 패키지가 바로 react-router 입니다.
- react-router
- react-router-dom
react-router 를 설치하면 자동으로 react-router-dom이 종속성으로 포함되어 설치됩니다. 실제 서비스에서 사용할때는
react-router-dom 만 import 하여 사용해야합니다. react-router 에서 정의된 메소드들이 react-router-dom 을 통해서 import 되어있기때문입니다.
순수 자바스크립트로 라우팅 구현해보기
SPA 을 만들때 어떻게 라우팅서비스를 구현할 수 있을지 이를 통해 리액트의 라우터에 대해 더 잘 이해하는 과정을 진행하려고 합니다.
SPA 구성을 위해 위와같이 index.html 을 생성하고 하나의 루트 엘리먼트와 스크립트를 생성합니다. 이때 스크립트 타입에 module 을 지정해줘야 import / export 구문을 사용할 수 있습니다.
- src/pages/index.js
- src/pages/Main/index.js
- src/pages/About/index.js
파일들을 각각 생성하고 위와같이 작성합니다.
결과화면입니다. 페이지 전환에 따라 변경사항이 있는 부분만 변하는걸 확인할 수 있습니다.
이제 url 변경을 통한 라우팅을 구현해야하는데 두가지 방법을 사용할 수 있습니다.
1. hash
window 객체중 location.hash 를 사용하는 방법입니다. 해시는 일반적으로 동일한 문서 내에서 다른 섹션으로 이동할때 많이 사용하는 방법입니다. <a href="#main"> 을 사용하게되면 <div id="main"> 이 위치한 곳으로 이동시킬 수 있습니다.
그리고 이벤트중 hashchange 를 활용하면 해시 변경에 대한 동작을 스크립트로 처리할 수 있습니다.
2. history
또 다른 방법은 history api 를 활용할 수 있습니다. history.pushstate 를 활용하면 url 주소를 변경할 수 있습니다.
다만 해당 api 는 url 주소만 변경하기때문에 페이지와 매칭시키기위한 처리가 필요합니다. 또한 뒤로가기나 앞으로 가기 동작을 수행했을때에 정상적으로 페이지가 변경되어야 합니다.
아주 간단한 예제로 두가지 방식을 활용하여 SPA 에서 사용할 수 있는 라우터 기능을 구현해봤습니다. 기본적인 원리 자체는 단순합니다. url 을 변경시키고 해당 url 과 매치되는 페이지를 동적으로 변경해주면 됩니다. 다만 두가지방식 약간의 문제점을 갖고있습니다.
첫번째 hash 방식은 주소에 # 이라는 불필요한 해쉬가 붙습니다. 또한 검색엔진에서 읽어들일 수 없기때문에 서비스를 제공하는 입장에서는 치명적인 단점으로 작용할 수 있습니다. react-router 에도 <HashRouter> 를 사용할 수 있지만 꼭 필요한 경우가 아니라면 사용을 지양하고 있습니다.
두번째 방식은 일반적인 동작은 정상적으로 동작하지만 새로고침했을경우 404 페이지를 마주할 수 있습니다. 해시는 현재 페이지 내부에서 찾는 방식이기때문에 새로고침했을때에도 정상동작하지만 history 같은경우 실제 서버로 요청을 보내기때문에 에러를 마주하게됩니다.
위의 문제들을 해결하기 이전에 리액트 라우터의 공식문서에 설명된 주요 개념을 살펴보려고 합니다.
리액트 라우터 주요개념
React Router 는 단순히 URL을 함수나 구성 요소에 일치시키는 것이 아닙니다. URL 에 매핑되는 전체 사용자 인터페이스를 구축하는것이라 더 많은 개념이 포함될 수 있습니다. 리액트 라우터의 세가지 주요 작업은 아래와 같습니다.
1. history stack 구독 및 조작
2. URL 을 경로에 일치시키기
3. 일치한 경로에서 중첩된 UI 렌더링
위 내용은 리액트 라우터 공식문서에서 주요개념에 설명된 내용입니다.
Vanila JS 로 간단한 라우터관련 코드를 작성할때 매우 단순한 원리로 작성했습니다. url 과 매칭되는 키를 확인하고 조건에 맞는 페이지를 랜더링 하면 되는 방식이었습니다. Vanila JS 로 히스토리관리를 할때 pushstate 와 popstate 를 활용할 수 있습니다. 다만 popstate 는 브라우저 인터페이스 상에있는 뒤로가기와 앞으로 가기만 감지할 수 있을뿐 pushstate 나 replaceState 일때 감지할 이벤트가 없습니다.
리액트 라우터에서는 history 패키지를 사용하고 있습니다. 해당 라이브러리는 자바스크립트가 실행되는 모든 곳에서 히스토리를 쉽게 관리할 수 있도록 해줍니다. 환경의 차이를 추상화하고 히스토리 스택을 탐색, 관리하고 세션간에 상태를 지속할 수 있는 api 를 제공합니다.
이 history 라이브러리를 사용함으로써 기본 api 를 사용할때 감지할 수 없던 부분까지 활용할 수 있게 되었습니다.
<BrowserRouter> 에서 createBrowserHistory({ window }) 를 통해 히스토리를 생성하고 초기 Location 상태로 설정하고 URL 을 구독합니다. createBrowserHistory 는 hisrory 라이브러리에서 제공하는 메소드입니다.
<Routes> 에서 경로구성을 빌드하기위해 자식경로를 재귀하고 해덩 경로를 Location 과 일치 시키고 첫번째 일치한 경로의 요소를 렌더링 합니다. createRoutesFromChildren 을 활용하여 React.Children 의 요소를 재귀적으로 처리합니다.
createRoutesFromChildren 는 리액트 라우터에서 정의하고 있습니다.
중첩된 경로에 대한 처리는 v6 에서 새로 추가된 <Outlet /> 을 사용하여 처리할 수 있습니다.
// xxxxxxxxx //
<Route path="user" element={<User />}></Route>
<Route path="user/detail" element={<UserDetail />}><Route>
// v6 //
<Route path="user" element={<User />}>
<Route path="detail" element={<UserDetail />}><Route>
</Route>
function User() {
return (
<div>
<h1>User</h1>
<Outlet />
</div>
)
}
기타 자세한 내용은 공식문서를 참고해주시면 될것같습니다.
다시 Vanila JS
다시 돌아와서 아직 해결하지 못한 문제가 하나 있습니다. history api 를 사용하는건 알겠는데 새로고침시에 페이지가 유지가 안되는 문제가 있습니다. 리액트가 어떻게 실행되는지 생각해보면 npm start 명령어를 통해 리액트를 실행시킬 서버를 동작시키는걸 알 수 있습니다.
해당 예제도 동일하게 webpack 을 활용하여 간단하게 개발서버를 생성하고 리다이렉트를 구현하려고 합니다.
npm init -y
npm i webpack webpack-cli webpack-dev-server -D
// package.json
"scripts": {
"dev": "webpack serve"
}
// webpack.config.js
var path = require(‘path’);
var HtmlWebpackPlugin = require(‘html-webpack-plugin’);
module.exports = {
mode: ‘none’,
entry: ‘./src/index.js’,
output: {
filename: ‘bundle.js’,
path: path.resolve(__dirname, ‘dist’),
},
devServer: {
port: 9000,
historyApiFallback: true
},
plugins: [
new HtmlWebpackPlugin({
template: ‘index.html’,
}),
],
};
위와같이 파일이 없다면 생성하고 코드를 작성하면 npm dev 를 실행시킬 수 있습니다. 9000번 포트에 정상적으로 띄워지면 성공한것입니다. 이제 url 이 변경되더라도 이전처럼 에러페이지가 나오지않고 정상적으로 현재 페이지에 머무는걸 확인할 수 있습니다.
webpack.config.js 에 historyApiFallback: true 는 404 에러가 발생했을때 index.html 로 리다이렉트 시키는 옵션입니다.
정리
ReactRouter는 history 라는 패키지를 활용하여 브라우저 interface 에있는 뒤로가기 앞으로가기 이외에 history api 에 대해서 캐치하여 활용한다.- SPA 에서는 url 에 따른 별도의 페이지가 없기때문에
index.html로 리다이렉션 하는webpack의historyApiFallback을 사용할 수 있다. - 리액트 라우터의 처리동작은 생각보다 복잡하다...
- Vanila JS 로 아주 간단한 SPA 환경과 라우터동작을 구현할 수 있다.
'프론트엔드 > Javascript' 카테고리의 다른 글
자바스크립트 배열 다루기 (0) | 2021.12.09 |
---|---|
Vanila Javascript Webpack Setting (0) | 2021.10.23 |
자바스크립트 기본개념(1) - 호이스팅 (0) | 2021.10.14 |