Vanilla JavaScript로 SPA(Single Page Application) 구현하기
SPA의 Routing은 history, hash를 통해 구현 가능하다.
각 방법에는 장단점이 있다.
Advantages
Advantages
해시 이후 경로 조각을 별도의 페이지로 간주하지 않으며, 새로고침 하면 index.html이 reload 되어 단일 페이지 앱에 적합하다.
해시가 정의된 링크는 페이지 reload가 발생하지 않으므로 보다 안전하다.
API와 프론트엔드 요청을 구별하기 쉽다.
Disadvantages
자세한 내용은 Using Hashed vs Non-Hashed URL Path in SPA 링크를 참고하면 좋을거 같다.
해당 블로그에서는 history (Non-Hashed) 방법을 사용했다.
router.js
import { $ } from './util/util.js';
import Home from './pages/Home.js';
import Life from './pages/Life.js';
import Trip from './pages/Trip.js';
const router = async () => {
const routes = [
{ path: '/', view: Home },
{ path: '/life', view: Life },
{ path: '/trip', view: Trip },
];
// routes 목록에서 현재 경로와 path가 일치하는 객체를 할당하여 해당 객체의 컴포넌트 Class를 생성한다.
let match = routes.find(route => route.path === location.pathname);
if (match) return match.view;
else new NotFound($('#root'));
};
export default router;
app.js
import { $ } from './util/util.js';
import Components from './core/Components.js';
import router from './router.js';
export default class App extends Components {
constructor(props) {
super(props);
}
template() {
return `
<div class="container">
...
<section id="app"></section>
</div>
`;
}
async componentDidMount() {
// 라우터를 실행시켜 현재 경로에 맞는 컴포넌트 클래스를 반환받은 후
// 해당 클래스 인스턴스를 생성하여 태그에 할당한다.
const view = await router();
view && new view($('#app'));
...
}
}
아래는 사용자가 Nav 아이템을 클릭하여 페이지를 이동시키는 컴포넌트 클래스 예시이다.
...
async componentDidMount() {
// history에서 항목을 pop하는 경우 (뒤로가기 버튼 클릭시) 처리
window.onpopstate = async () => {
// 현재 경로를 다시 읽고 app 태그에 경로에 맞는 클래스의 template을 넣는다.
const view = await router();
view && new view($('#app'));
};
// menu ul 태그에 이벤트 위임을 사용하여 클릭 이벤트를 등록한다.
$('.category_menu_ul').addEventListener('click', async event => {
console.log(event);
event.preventDefault();
// closest를 사용하여 타겟과 가까운 li를 찾아 data-cm 속성을 읽고
// 해당 속성 경로를 history에 push한다.
const { cm } = event.target.closest('li').dataset;
const { pathname } = window.location;
console.log(pathname);
if (pathname === '/' + cm || (pathname === '/' && cm === 'home')) return;
console.log(cm);
window.history.pushState(null, '', cm === 'home' ? '/' : cm);
// 해당 속성 경로를 Push한 뒤 라우터를 실행한다.
const view = await router();
view && new view($('#app'));
});
}
...