본문 바로가기

Javascript/응용프로젝트

[2] 자바스크립트로 투두리스트(todo-list) 만들기

 

자바스크립트로 투두리스트 (todo-list) 만들기 2편입니다.

완성본과 HTML 과 CSS 는 1편에서 확인해주세요

1편 https://qowbtm45.tistory.com/44


기능 설명 (2편)

1. router 기능 구현

2.카테고리 클릭 시 카테고리에 맞는 주소로 이동하게 하기

 

라우팅 (routing)에 대해서

- 출발지에서 목적지까지의 경로를 결정하는 기능

- 일반적으로 사용자가 요청한 URL 또는 이벤트를 해석 => 새로운 페이지로 전환하기 위해 데이터를 서버에 요청하고 페이지를 전환하는 일련의 행위

- 애플리케이션의 라우팅 경우에는 사용자가 task 를 수행하기 위해 어떤 화면 (view) 에서 다른 화면으로 화면을 전환하는 내비게이션을 관리하기 위한 기능을 의미함

 

브라우저가 화면을 전환하는 경우

1. 브라우저의 주소창에 URL 을 입력하면 해당 페이지로 이동

2. 웹 페이지의 링크 (a 태그) 를 클릭하면 해당 페이지로 이동

3. 브라우저의 뒤로가기 또는 앞으로 가기 버튼을 클릭하면 사용자 방문 기록(history) 의 뒤 또는 앞으로 이동

( history 관리를 위해서는 각 페이지는 브라우저의 주소창에서 구별할 수 있는 유일한 URL을 소유해야함 )


Router 기능 구현하기

 

routes

  routes = [];
1. url 과 callback 이 담길 빈 배열 (default)

 

notFoundCallback ()

  notFoundCallback = () => {};
1. 원하는 라우트가 없을 경우 실행하는 콜백함수를 클래스 변수로 추가 (default)

 

addRoute ()

  addRoute(url, callback) {
    this.routes.push({
      url,
      callback,
    });
    return this;
  }
1. 해당되는 url 을 진입했을 때 콜백을 실행함
2. routes 에 url 과 callback 을 넣어줌 (push)
3. 인스턴스 생성 시 체이닝을 사용할 수 있도록 this를 return

addRoute () 의 this를 콘솔에 찍은 모습

 

인스턴스 (instance)
- 객체 지향 프로그래밍 (OOP) 에서 클래스에 소속된 개별적인 객체
- 하나의 클래스를 사용하여 유사 성질을 가진 수많은 인스턴스를 생성 가능
- 클래스에서 실제 객체를 생성하는 것을 인스턴스화 (instantiation) 라고 함
체이닝 (chaining) or  체이닝 패턴
- 객체에 연쇄적으로 메서드를 호출할 수 있도록 하는 패턴
- 여러가지 동작을 수행할 때, 먼저 수행한 동작의 반환 값을 변수에 할당한 후 다음 작업을 할 필요가 없음
=> 호출을 여러줄에 걸쳐 쪼개지 않아도 된다.
- 메서드에 의미있는 반환 값이 존재하지 않는다면, 현재 작업중인 객체 인스턴스인 this 를 반환

체이닝 (chaining) or  체이닝 패턴의 장점과 단점
- 장점

1. 코드량이 줄어든다 => 코드가 간결해져서 하나의 문장처럼 읽히게 할 수 있음
2. 함수를 쪼개는 방법을 생각하게 된다. ( 유지보수 개선 )

- 단점
1. 디버깅 하기 어렵다 => 코드의 어느 라인에서 에러가 발생했는지 알아내도, 그 라인에서 수행하는 일이 너무 많을 수 있음

 

checkRoute ()

  checkRoute() {
    const currentRoute = this.routes.find(
      (route) => route.url === window.location.hash
    );

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

    currentRoute.callback();
  }
 1. currentRoute 라는 현재 라우트를 저장할 곳을 만들어준다. (routes array) 에서 찾아야 하는 값
2. 만들어놓은 routes 배열 ( 라우트들이 저장된 곳 )에서 route.url 과 window.location.hash ( 현재 주소 )와 같은 route 를 반환
3. 만약에 currentRoute 가 없다면 ( 저장된 라우트 url 과 현재 주소가 맞는게 없는 경우 ) notFoundCallback() 을 실행
=> notFoundCallback() 은 밑에서
4. 맞는 라우트를 찾으면 , 그 라우트의 callback 을 실행하게 됨

currentRoute 를 콘솔에 찍은 모습
window.location.hash 를 콘솔에 찍은 모습

window.location.hash
- 현재 url 의 # 뒤에 있는 string 을 반환해줌

 

init ()

  init() {
    window.addEventListener("hashchange", this.checkRoute.bind(this));
    if (!window.location.hash) {
      window.location.hash = "#/";
    }
    this.checkRoute();
  }
1. 라우트를 초기화해주는 함수
2. hashchange 이벤트로 checkRoute () 를 실행해준다 ( 초기화 되었을 때 )
3. 만약에 현재 주소 값이 없을 경우에는, 기본 값으로 '#/' 을 넣어준다.

hashchange 의 event 를 콘솔에 찍은 모습

 

setNotFound ()

  setNotFound(callback) {
    this.notFoundCallback = callback;
    return this;
  }
1. noFoundCallback 함수에 빈 함수를 추가해줌
2. addRoute() 와 동일하게 체이닝을 사용할 수 있도록 this 를 return 한다.

Router 인스턴스 추가

 

DOMContentLoaded 이벤트에 Router 인스턴스 추가

document.addEventListener("DOMContentLoaded", () => {
  const router = new Router();
	...
});

 

routeCallback

document.addEventListener("DOMContentLoaded", () => {
	...
  const routeCallback = (status) => () => {
    todoList.filterTodo(status);
    document.querySelector(
      `input[type='radio'][value='${status}']`
    ).checked = true;
  };
});
1. hash 로 받은 ALL/TODO/DONE 을 상태값(status) 를 받음
2. 상태값을 필터링하는 filterTodo에 파라미터로 status 를 전달해줌
3. 콜백이 실행될 때마다 (url 이 바뀔 때마다) 카테고리의 버튼이 활성화된 상태인 input 을 찾음 (각각 실행)
4. routeCallback 에 함수를 넣어버리는 순간, 바로 함수가 실행된다
=> 클로저를 활용해 status를 가진 채로 함수가 함수를 리턴을 하도록 한다
=> routeCallback 에서 url 과 status 를 담은 실행되지 않는 함수가 리턴됨.
클로저(Closure)
- 함수와 함수가 선언된 어휘적 환경의 조합

- "함수"란 반환된 내부함수를 의미함
- 그 함수가 선언될 때의 렉시컬 환경 (Lexical environment) 란 내부 함수가 선언됐을 때의 스코프를 의미함
- 즉 자신이 생성될 때의 환경 (렉시컬 환경 (Lexical environment)) 을 기억하는 함수

클로저를 사용하는 이유
- 전역변수를 줄일 수 있음
=> 함수 하나에 사용하는 전역변수가 필요한 순간이 있음. 그때 유용하게 사용됨
- 코드의 가독성도 좋은 재사용하기 편한 코드를 구현할 수 있음

참고 영상 https://www.youtube.com/watch?v=MbYShFxp-j0

 

Router 의 메서드에 상태값 전달

document.addEventListener("DOMContentLoaded", () => {
	...
  router
    .addRoute("#/all", routeCallback("ALL"))
    .addRoute("#/todo", routeCallback("TODO"))
    .addRoute("#/done", routeCallback("DONE"))
    .setNotFound(routeCallback("ALL"))
    .init();
});
1. router 클래스의 addRoute() 에 카테고리의 hash 값을 정해서 전달
2. callback 에는 위에서 만든 routeCallback 함수를 넣어주도록 함
=> 상태값은 위 코드와 같이 전달
3. setNotFound() 는 위 3개 상태값에 해당되지 않을 때 그냥 ALL 상태값을 갖는 콜백을 실행할 수 있도록 함
4. init () 을 추가해, hashchange 이벤트를 감지할 수 있도록 추가함

todoList 클래스 메서드 수정

 

onClickRadioBtn ()

  onClickRadioBtn(event) {
    const { value } = event.target;
    // this.filterTodo(value);
    window.location.href = `#/${value.toLowerCase()}`;
  }
1. filterTodo 에는 status를 전달해주기 때문에, value 를 전달해주는 코드는 없애준다.
=> onClickRadionBtn 클릭 시 기존의 filterTodo() 는 Router 쪽에서 콜백으로 사용됨
2. window.location.href 에 클릭한 카테고리의 값을 템플릿 리터널을 사용해서 소문자로 바꿔주고 #/ 을 붙여준 url 로 이동할 수 있게 한다.
window.location.href
- href 는 location 객체에 속해있는 프로퍼티로 현재 접속중인 페이지 정보를 갖고 있음
- 또한 값을 변경할 수 있는 프로퍼티이기 때문에 다른 페이지로 이동하는데도 사용되고 있음

전체 기능 구현 코드