두 번째는 url parameter를 활용하는 것이다.
/js-spa/frontend/static/js/index.js
import Dashboard from "./views/Dashboard.js";
import Posts from "./views/Posts.js";
import PostView from "./views/PostView.js";
import Settings from "./views/Settings.js";
//path에서 "/"가 들어가있으면 "\/"로 바꾸고, :로 시작하는 단어 영문자가 있으면 (.+)로 바꿔라
const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$")
const getParams = match => {
//match.result: ['/posts/2', '2', index: 0, input: '/posts/2', groups: undefined]
const values = match.result.slice(1);
//Array.from(match.route.path.matchAll(/:(\w+)/g)): [':id', 'id', index: 7, input: '/posts/:id', groups: undefined]
const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(result => result[1]);
return Object.fromEntries(keys.map((key, i) => {
return [key, values[i]]
}));
}
const navigateTo = url => {
history.pushState(null, null, url);
router();
}
const router = async () => {
// pathToRegex("/post/2"): ^\/post\/2$/
// pathToRegex("/posts/:id"): /^ \/post\/(.+) $/
const routes = [
{ path: "/", view: Dashboard },
{ path: "/posts", view: Posts },
{ path: "/posts/:id", view: PostView },
{ path: "/settings", view: Settings },
];
const potentialMatches = routes.map(route => {
return {
route: route,
result: location.pathname.match(pathToRegex(route.path))
}
});
let match = potentialMatches.find(potentialMatche => potentialMatche.result !== null);
if(!match) {
match = {
route: routes[0],
result: [location.pathname]
};
}
//view 객체 생성 시 getParams() 메소드에서 반환하는 [key, value] 배열 전달
const view = new match.route.view(getParams(match));
document.querySelector("#app").innerHTML = await view.getHtml();
};
window.addEventListener("popstate", router);
document.addEventListener("DOMContentLoaded", () => {
document.body.addEventListener("click", e => {
if(e.target.matches("[data-link]")) {
e.preventDefault();
navigateTo(e.target.href);
}
})
router();
})
Object.fromEntries()
- key, value 목록을 받아 객체로 반환
("문자열").match(/정규표현식/플래그)
- "문자열"에서 "정규표현식"에 매칭되는 항목들을 배열로 반환
/js-spa/frontend/static/js/views/AbstractView.js
export default class {
constructor(params) {
//객체 생성 시 전달한 [key, value] 배열 받아와서 저장
this.params = params;
//지금은 console로 찍고만 있지만, 저장해서 활용 가능
console.log(this.params); //{id: '2'}
}
setTitle(title) {
document.title = title;
}
async getHtml() {
return "";
}
}
/js-spa/frontend/static/js/views/Dashboard.js
import AbstractView from "./AbstractView.js";
export default class extends AbstractView {
constructor(params) {
super(params); //params 저장
this.setTitle("Dashboard");
}
async getHtml() {
return `
<h1>Welcome back, Dom</h1>
<p>Dashboard Page</p>
<p><a href="/posts" data-link>View recent posts</a></p>
`;
}
}
라우터를 직접 구현해보는 것도, 관련 강의를 본 것도 처음이라 해당 코드가 best 코드인지 아직 판단이 안되지만, 내가 아직 낯설어서인지 조금 복잡하게 느껴졌다. 라우터 및 url 관련해서 더 찾아보고 조금 더 간단하게 구현할 수 있는 부분이 있으면 수정도 해봐야겠다 !