바닐라 js로 browser router 만들기

SPA를 만들기 위해선 router 구현이 필요하다.

vanillaJS로 browser router를 구현해보았다.

기본적으로 SPA의 동작원리는 다음과 같다.

key를 url path로 value를 component로 하는 객체 리터럴을 만든다.

페이지 이동을 할 때에 이 객체 리터럴에서 페이지에서 파싱한 값을 통해 component를 조회하여 렌더링한다

페이지 이동을 정의하는 방법엔 두 가지가 있다. 함수를 통해 pushState로 바꿔주고 innerHTML을 바꾸어주거나 혹은 이 작업을 커스텀 이벤트로 정의하는 방식이다.

두 번째( 커스텀 이벤트를 정의하는 방식 )으로 browser router를 구현해보았다.

Router 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Router {
constructor() {}
// 생성자
initEvent() {}
// 이벤트 초기화
onRouterChangeHandler() {}
// route가 변경되었을 때의 이벤트 핸들러
hasRoute() {}
// 올바른 라우트인지 검증
getRoute() {}
// 해당 라우트 가져오기
renderPage() {}
// 페이지 렌더링
push() {}
// 라우터 push
}

Router 초기화

1
2
3
4
5
6
7
8
9
10
11
12
13
constructor({
$app,routes,fallback='/'
}){
this.$app=$app;
this.fallback=fallback;
this.routes=[];

routes.forEach(route=>{
this.routes=[route.path]=route.page;
})

this.initEvent();
}

$app,fallback,route를 초기화하고 initEvent를 실행한다.

커스텀 이벤트 추가

browser router는 history API를 사용하여 주소를 변경한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
initEvent() {
document.addEventListener(
"moveRoutes",
this.moveRoutesHandler.bind(this) as EventListener
);
}

moveRoutesHandler(event: CustomEvent) {
const path: string = event.detail.path;
history.pushState(event.detail, "", path);

this.renderPage(path);
}

moveRoutes라는 이벤트를 추가해주어야 한다.

1
2
3
4
5
6
7
export const customEventEmitter = (eventType: string, detail?: object) => {
document.dispatchEvent(
new CustomEvent(eventType, {
detail,
})
);
};

커스텀 이벤트를 정의하는 함수를 만든다.

페이지 렌더링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
hasRoute(path: string) {
return typeof this.routes[path] !== "undefined";
}

getRoute(path: string) {
return this.routes[path];
}

renderPage(path: string) {
let route;
const regex = /\w{1,}$/;

if (this.hasRoute(path)) {
route = this.getRoute(path);
// 경로가 존재할경우
} else if (regex.test(path)) {
route = this.getRoute(path.replace(regex, ":id"));
// 경로가 존재하지 않을 경우 동적 라우팅(id)
} else {
route = this.getRoute(this.fallback);
// 그 외의 주소에 대해서는 fallback 실행
}
new route(this.$app, {});
// route 객체의 인스턴스를 만듬
}

push(path: string) {
customEventEmitter("moveRoutes", {
...history.state,
path,
});
}

라우터 export

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export let router;

export function initRouter($app, routes, fallback) {
const routerObj = new Router($app, routes, fallback);

router = {
push: (path) => routerObj.push(path),
};

customEventEmitter(
"moveRoutes",
history.state ?? {
path: "/",
}
);
}

모듈패턴을 이용하여 push를 제외한 나머지는 private로 두었다.

그리고 초기에 customEventEmitter를 통해 루트 페이지가 렌더링 될 수 있도록 하였다.

사용하기

1
2
3
4
5
6
7
const routes = [
{ path: "/", page: App },
{ path: "/detail/:id", page: Detail },
];

const target = document.querySelector(".App");
initRouter(target, routes);

Ref

바닐라JS(TS)로 리액트 SPA 구현하기 | (4) 클래스로 BrowserRouter 구현
Build a custom SPA Router using VanillaJS

댓글