Vanila JS로 SPA 구현하기Ⅱ

miyaongg·2023년 3월 27일
0
post-thumbnail

두 번째는 url parameter를 활용하는 것이다.

1. index.js 파일을 수정해보자

/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(/정규표현식/플래그)

  • "문자열"에서 "정규표현식"에 매칭되는 항목들을 배열로 반환

2. parameter 저장하기

/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>
        `;
    }
}

따라해보면서 제일 헷갈렸던 부분은 정규표현식 부분이었다..
지금까지는 정규표현식을 form 요소들을 사용할 때 유효성 체크하는 부분에서만 찾아 쓰는 정도로만 사용했었는데, 이번에 정규표현식부터 분석하면서 이해하려니까 꽤나 모르는 부분이 많았다.

그래서 다음엔 정규표현식도 정리해봐야 할 것 같다...ㅎㅎ...

라우터를 직접 구현해보는 것도, 관련 강의를 본 것도 처음이라 해당 코드가 best 코드인지 아직 판단이 안되지만, 내가 아직 낯설어서인지 조금 복잡하게 느껴졌다. 라우터 및 url 관련해서 더 찾아보고 조금 더 간단하게 구현할 수 있는 부분이 있으면 수정도 해봐야겠다 !



강의: https://youtu.be/OstALBk-jTc

profile
Front-End Developer

0개의 댓글