30개의 프로젝트로 배우는 프론트엔드 with VanillaJS (9-3) To-Do-List

productuidev·2022년 11월 1일
1

FE Study

목록 보기
67/67
post-thumbnail

30개의 프로젝트로 배우는 프론트엔드 with VanillaJS (9-3) To-Do-List

(9) To-Do-List

07) router 개발 및 적용

복습 : 라우팅(routing)

라우팅이란 출발지에서 목적지까지의 경로를 결정하는 기능이다. 애플리케이션의 라우팅은 사용자가 태스크를 수행하기 위해 어떤 화면(view)에서 다른 화면으로 화면을 전환하는 내비게이션을 관리하기 위한 기능을 의미한다. 일반적으로 라우팅은 사용자가 요청한 URL 또는 이벤트를 해석하고 새로운 페이지로 전환하기 위해 필요한 데이터를 서버에 요청하고 페이지를 전환하는 위한 일련의 행위를 말한다.

브라우저가 화면을 전환하는 경우는 다음과 같다.
1. 브라우저의 주소창에 URL을 입력하면 해당 페이지로 이동한다.
2. 웹페이지의 링크(a 태그)를 클릭하면 해당 페이지로 이동한다.
3. 브라우저의 뒤로가기 또는 앞으로가기 버튼을 클릭하면 사용자 방문 기록(history)의 뒤 또는 앞으로 이동한다. history 관리를 위해서는 각 페이지는 브라우저의 주소창에서 구별할 수 있는 유일한 URL을 소유해야 한다.

  • 전통적 링크 방식 : link tag를 클릭하면 href 어트리뷰트 값인 리소스 경로가 URL의 path에 추가되어 주소창에 나타나고 해당 리소스를 서버에 요청 (서버 사이드 렌더링(SSR))
  • Ajax 방식 : 서버로부터 웹페이지가 응답되면 화면 전체를 새롭게 렌더링해야 하는데 페이지 일부만 갱신하고도 동일한 효과를 볼 수 있도록 하는 것. 내비게이션 클릭 이벤트를 캐치하고 preventDefault 메서드를 사용해 서버로의 요청을 방지한다. 이후, href 어트리뷰트에 path을 사용하여 ajax 요청을 하는 방식. 필요한 리소스 중복 요청을 방지할 수 있고 새로고침이 없는 사용자 경험을 구현할 수 있다는 장점이 있지만 history 관리가 되지 않는 단점. SEO 이슈 존재.
  • Hash 방식 : Ajax를 보완한 방식. URI의 fragment identifier(#service)의 고유 기능인 앵커(anchor)를 사용. 내비게이션이 클릭되면 hash가 추가된 URI가 주소창에 표시된다. 단, URL이 동일한 상태에서 hash만 변경되면 브라우저는 서버에 어떠한 요청도 하지 않는다. 즉, URL의 hash는 변경되어도 서버에 새로운 요청을 보내지 않으며 따라서 페이지가 갱신되지 않는다. hash는 요청을 위한 것이 아니라 fragment identifier(#service)의 고유 기능인 앵커(anchor)로 웹페이지 내부에서 이동을 위한 것이기 때문이다. 또한 hash 방식은 서버에 새로운 요청을 보내지 않으며 따라서 페이지가 갱신되지 않지만 페이지마다 고유의 논리적 URL이 존재하므로 history 관리에 아무런 문제가 없다. hash 방식은 url의 hash가 변경하면 발생하는 이벤트인 hashchange 이벤트를 사용해 hash의 변경을 감지하고 url의 hash를 취득해 필요한 ajax 요청을 수행한다. hash 방식의 단점은 url에 불필요한 #이 들어간다는 것이다. 일반적으로 hash 방식을 사용할 때 #!을 사용하기도 하는데 이를 해시뱅(Hash-bang)이라고 부른다. SEO 이슈 존재.
  • Pjax 방식 : HTML5의 History API인 pushState와 popstate 이벤트를 사용한 pjax(pushState + ajax) 방식. pjax 방식에서 사용하는 history.pushState 메서드는 주소창의 url을 변경하지만 HTTP 요청을 서버로 전송하지는 않는다. 따라서 페이지가 갱신되지 않는다. 하지만 페이지마다 고유의 URL이 존재하므로 history 관리에 아무런 문제가 없다. 또한 hash를 사용하지 않으므로 SEO에도 문제가 없다. pjax 방식은 서버 렌더링 방식과 ajax 방식이 혼재되어 있는 방식으로 서버의 지원이 필요

출처 - 모던 자바스크립트 Deep Dive : Single Page Application & Routing

filter의 ALL / TODO / DONE을 눌렀을 때 hash 방식의 라우터 구현
hashchange 이벤트를 통해 hash 변경을 캐치할 수 있음

window.addEventListener('hashchange', (e) => console.log(e))

Router 클래스 생성

  • routes : url과 callback이 담긴 빈 배열(default)
  • notFoundCallback : 원하는 라우트가 없을 때 실행하는 콜백을 클래스 변수로 추가(default)
  • addRoute : 해당되는 url 진입 시 콜백 실행, routes에 url과 callback push, 인스턴스 생성 시 체이닝을 사용할 수 있도록 return (ex : Router.addRoute().addRouter())
  • checkRoute : 해당 라우트가 맞는지 체크, currentRoute는 routes array에서 찾아야 하는 값, 현재 #값과 현재 url을 대조해서 맞는 route를 return, 이 때 원하는 라우터가 없을 때 처리하는 조건으로 currentRoute가 존재하지 않으면 notFoundCallback 실행, 맞으면 콜백 실행하게 됨
  • init : 라우트 초기화, hashchange 이벤트로 checkRoute 실행(초기화 되었을 때), 현재 #값이 없는 경우 default로 '#/'을 넣어줌.
  • setNotFound : notFoundCallback에 빈 함수 추가, 여기서도 동일하게 체이닝을 사용할 수 있도록 return this 추가
class Router {
  routes = [];
  notFoundCallback = () => {};

  addRoute(url, callback) {
    this.routes.push({
      url,
      callback,
    });
    return this; 
  }
  
  checkRoute() {
    const currentRoute = this.routes.find(
      route => route.url === window.location.hash, 
    );

    if (!currentRoute) {
      this.notFoundCallback();
      return;
    }
    currentRoute.callback();
  }

  init() {
    window.addEventListener('hashchange', this.checkRoute.bind(this));
    if (!window.location.hash) {
      window.location.hash = '#/';
    }

    this.checkRoute();
  }

  setNotFound(callback) {
    this.notFoundCallback = callback;
    return this;
  }
}

Router 인스턴스 추가

  • DOMContentLoaded 이벤트에 Router 인스턴스 추가하기
  • Tip : 프론트엔드 면접 때 종종 SPA Routing에 대해 묻는 경우가 있음 (라우터 구현 원리)
  • routeCallback : 해시로 받을 ALL/TODO/DONE 상태값(status)을 받음. 상태값을 필터링하는 filterTodo의 status를 담고, 콜백이 실행될 때마다(=url이 바뀔 때마다) input type='radio' value='status'가 checked(활성)된 상태인 input을 찾음 (각각 실행)
  • routeCallback에서 함수를 넣어버리는 순간 함수가 실행되므로 클로저를 활용해 status를 가진 채로 함수가 함수를 리턴하도록 변경 (ex : routeCallback에서 url과 상태값을 담은 실행되지 않는 함수가 리턴됨)
  • setNotFound 메소드는 위 3개 상태값에 해당되지 않을 때 그냥 ALL 상태값을 갖는 콜백 실행
  • init을 추가해 hashchange를 감지하도록 추가
document.addEventListener('DOMContentLoaded', () => {
  const router = new Router();
  const todoList = new TodoList();

  const routeCallback = status => () => {
    todoList.filterTodo(status);
    document.querySelector(
      `input[type='radio'][value='${status}']`,
    ).checked = true;
  };
  
  router
    .addRoute('#/all', routeCallback('ALL'))
    .addRoute('#/todo', routeCallback('TODO'))
    .addRoute('#/done', routeCallback('DONE'))
    .setNotFound(routeCallback('ALL'))
    .init();
});

참고 : closure

상태값 클릭 시 URL 변경

  • onClickRadioBtn 클릭 시 기존의 filterTodo는 router쪽에서 콜백으로 사용하므로 주석처리
  • 각 버튼 클릭 시 클릭한 라우터 url을 window.location.href에 소문자로 표시
  onClickRadioBtn(event) {
    const { value } = event.target;
    // console.log(value);
    // this.filterTodo(value);

    window.location.href = `#/${value.toLowerCase()}`;
  }

중간 결과

기타 참고자료

JavaScript로 라우터를 만들어 간단한 SPA를 구현해보기
SPA에서 어떻게 페이지의 이동이 이루어질까? (hash mode, history mode)
VanillaJS를 이용한 SPA에서 라우팅을 구현하는 방법 (feat. history API)
[JavaScript] HTML5 History API, history 패키지 (feat. react-router-dom)


💬 라우팅 복습 하면서 딥다이브 복습.. 강의는 23분 짜리라 다 설명해줄 수 없어 다시 찾아보았다.
이번 강의를 통해 몇 가지 개념에 대해서는 내가 잘 모르고 있음을 깨닫게 되었다.
실 예제를 보고서 모르는 것을 맞닥뜨리며 서칭한 자료를 토대로 학습중...

profile
필요한 내용을 공부하고 저장합니다.

0개의 댓글