투두 등록하기

1. 투두 등록 폼 만들기

// index.js
const $app = document.getElementById("app");

const $form = document.createElement("form");
const $input = document.createElement("input");
const $button = document.createElement("button");
$input.placeholder = "투두를 입력하세요.";
$button.textContent = "등록하기";

$form.appendChild($input);
$form.appendChild($button);
$app.appendChild($form);
RegisterTodo

createElement() 메서드와 appendChild() 메서드를 사용하여 요소 노드를 만들고 DOM에 추가한다.


2. 폼 전송 시 input 초기화

function App() {
  const addTodo = () => {
    const todo = $input.value;
    $input.value = "";
  };
  $form.addEventListener("submit", (e) => {
    e.preventDefault();
    addTodo();
  });
}

new App();

$form 요소 노드에 submit 이벤트를 등록한다. preventDefault() 메서드를 사용하여 DOM 요소의 기본 동작을 중단하고 addTodo() 함수를 실행한다. 이곳에서는 $input 요소 노드의 valuetodo 변수에 할당하고 value를 빈 문자열로 바꾼다.


3. 투두 데이터 업데이트

function App() {
  let todos = [];

  const addTodo = () => {
    const todo = $input.value;
    todos.push({ todo, done: false });
    $input.value = "";
  };
  $form.addEventListener("submit", (e) => {
    e.preventDefault();
    addTodo();
  });
}

new App();

빈 배열의 todos를 선언한다. todos는 앞으로 관리할 투두 리스트가 된다. addTodo 함수에서 push() 메서드를 사용하여 tododone(기본값으로 false)를 담은 객체를 추가한다.


4. 투두 데이터 업데이트에 따른 View(DOM) 업데이트

function App() {
  let todos = [];

  const addTodoView = (todo) => {
    if (!document.querySelector(".todo-list")) {
      const $ul = document.createElement("ul");
      $ul.classList.add("todo-list");
      $app.appendChild($ul);
    }
    const $todoList = document.querySelector(".todo-list");
    const $li = document.createElement("li");
    $li.id = todos.length;
    $li.style.color = "black";
    $li.textContent = todo;
    $todoList.appendChild($li);
  };

  const addTodo = () => {
    const todo = $input.value;
    todos.push({ todo, done: false });
    $input.value = "";
    addTodoView(todo);
  };
  $form.addEventListener("submit", (e) => {
    e.preventDefault();
    addTodo();
  });
}

new App();

addTodoView() 함수를 통해 View(DOM)를 업데이트한다. 해당 함수는 addTodo() 함수가 실행된 후 호출되어 실행된다.

RegisterTodo2

5. MVC 패턴으로 분리 및 리팩토링

티렉토리 구조

├── src
│   ├── controllers
│   │   ├── controller.js
│   ├── models
│   │   ├── todos.js
│   ├── utils
│   │   └── dom.js
│   ├── views
│   │   ├── view.js
├── index.html
└── index.js

5-1. Utils Function

파일 위치: src/utils/dom.js

DOM과 관련된 변수, 함수를 관리하기 위한 파일이다. 해당 파일엔 <div id="app"></div> 노드를 가져와 할당한 변수 $app와 요소 노드를 취득하기 위한 함수 $가 있다.

export const $app = document.getElementById("app");
export const $ = (id) => document.getElementById(id);

5-2. View

파일 위치: src/views/view.js

import { $, $app } from "../utils/dom.js";

export default class View {
  constructor() {
    this.render();
  }
  render() {
    const template = `
        <form id="todo-form">
            <input 
                placeholder="투두를 입력하세요." 
                id="todo-input"
                autocomplete="off"
            />
            <button>등록하기</button>
        </form>
        <ul id="todo-list">
        </ul>
    `;
    $app.innerHTML = template;
  }

  static todoRender = (todos) => {
    const template = todos
      .map(({ todo, done }, index) => {
        return `<li id=${index}>
            <span>${todo}</span>
            <button id="todo-done-btn">${done ? "완료함" : "완료"}</button>
            <button id="todo-delete-btn">삭제</button>
        </li>
        `;
      })
      .join("");
    $("todo-list").innerHTML = template;
  };
}

View는 크게 두 가지의 역할을 한다. 처음 생성될 때 render() 함수를 실행하여 뼈대가 되는 html를 그린다. 이때 todo를 등록할 수 있는 form 태그와 등록된 todo가 그려질 ul 태그가 생성된다.

todoRender() 함수는 static으로 사용한다. 해당 함수를 호출하면 생성된 모든 투두를 todos로 받아 목록을 그린 후 innerHTML 프로퍼티를 사용하여 todo-list의 자식 노드로 생성한다.


5-3. Model

파일 위치: src/models/todos.js

import { $ } from "../utils/dom.js";

export default class Todos {
  constructor() {
    this.todos = [];
  }

  addTodo = () => {
    const todo = $("todo-input").value;
    $("todo-input").value = "";
    this.todos.push({ todo, done: false });
    return this.todos;
  };
}

todos를 위한 Model이다. todos의 관리를 위해 constructor에서 this.todos를 배열로 선언한다.

addTodo() 함수는 현재의 input의 값을 바탕으로 새로운 todo를 추가할 때 사용된다. 반환하는 값은 추가된 todo가 포함된 todos이다.


5-4. Controller

파일 위치: src/controllers/controller.js

import Todos from "../models/todos.js";
import { $ } from "../utils/dom.js";
import View from "../views/view.js";

export default class Controller {
  constructor() {
    this.todosModel = new Todos();
    this.view = new View();
    this.eventHandler();
  }

  addTodo = () => {
    this.todosModel.addTodo();
    View.todoRender(this.todosModel.todos);
  };

  eventHandler = () => {
    $("todo-form").addEventListener("submit", (e) => {
      e.preventDefault();
      this.addTodo();
    });
  };
}

Controller에서는 ViewModel를 연결해주고 이벤트 핸들러를 등록 및 실행한다.

현재 등록된 이밴트 핸들러는 todo-form 아이디를 가진 formsubmit되었을 때 todo를 추가하는 것 뿐이다. 해당 이벤트가 실행되면 addTodo() 함수가 호출되어 Modeltodos 업데이트하고 업데이트 된 todos를 바탕으로 View.todoRender() 함수가 실행된다.


6. Conclusion

지금까지 바닐라 자바스크립트를 사용하여 웹을 만들 땐 MVC로 나누지 않고 한 곳에 모아 구현을 해 왔다. 비슷한 기능의 함수가 따로 파일을 만들어 import하는 방식으로 나누었을 뿐이다. 이번 기회에 MVC 패턴으로 나누어 봤는데, 일단 제대로 나누었는지 부터 의문이 든다. 하지만 나름 고민하면서 어떻게 하면 효과적으로 나눌 수 있는지 고민을 하였다. 앞으로 계속 공부를 하면서 MVC 패턴을 지키며 코드 구현을 해보도록 하자.


📅 2022-10-20

Last updated