💭
JavaScript PrimerのTodoアプリ作成でJS復習(リファクタリング)
記事概要
JSの復習として、JavaScript PrimerのTodoアプリ作成をやってみて、結構いい復習になったので紹介
この続き
対象読者
JavaScriptの基本的な構文は知っている初学者向けです。
復習内容
Viewのリファクタ
- App.js
todoListView.createElementのようにtodoListViewへリファクタリングする
App.js
import { TodoListModel } from "./model/TodoListModel.js";
import { TodoItemModel } from "./model/TodoItemModel.js";
import { TodoListView } from "./view/TodoListView.js";
import { render } from "./view/html-util.js";
export class App {
#todoListModel = new TodoListModel();
mount() {
const formElement = document.querySelector("#js-form");
const inputElement = document.querySelector("#js-form-input");
const containerElement = document.querySelector("#js-todo-list");
const todoItemCountElement = document.querySelector("#js-todo-count");
this.#todoListModel.onChange(() => {
const todoItems = this.#todoListModel.getTodoItems();
const todoListView = new TodoListView();
// todoItemsに対応するTodoListViewを作成する
const todoListElement = todoListView.createElement(todoItems, {
// Todoアイテムが更新イベントを発生したときに呼ばれるリスナー関数
onUpdateTodo: ({ id, completed }) => {
this.#todoListModel.updateTodo({ id, completed });
},
// Todoアイテムが削除イベントを発生したときに呼ばれるリスナー関数
onDeleteTodo: ({ id }) => {
this.#todoListModel.deleteTodo({ id });
}
});
render(todoListElement, containerElement);
todoItemCountElement.textContent = `Todoアイテム数: ${this.#todoListModel.getTotalCount()}`;
});
formElement.addEventListener("submit", (event) => {
event.preventDefault();
this.#todoListModel.addTodo(new TodoItemModel({
title: inputElement.value,
completed: false
}));
inputElement.value = "";
});
}
}
- TodoListView.js
各TodoItemモデルに対応したHTML要素は、TodoItemViewにリファクタ
TodoListView.js
import { element } from "./html-util.js";
import { TodoItemView } from "./TodoItemView.js";
export class TodoListView {
/**
* `todoItems`に対応するTodoリストのHTML要素を作成して返す
* @param {TodoItemModel[]} todoItems TodoItemModelの配列
* @param {function({id:number, completed: boolean})} onUpdateTodo チェックボックスの更新イベントリスナー
* @param {function({id:number})} onDeleteTodo 削除ボタンのクリックイベントリスナー
* @returns {Element} TodoItemModelの配列に対応したリストのHTML要素
*/
createElement(todoItems, { onUpdateTodo, onDeleteTodo }) {
const todoListElement = element`<ul></ul>`;
// 各TodoItemモデルに対応したHTML要素を作成し、リスト要素へ追加する
todoItems.forEach(todoItem => {
const todoItemView = new TodoItemView();
const todoItemElement = todoItemView.createElement(todoItem, {
onDeleteTodo,
onUpdateTodo
});
todoListElement.appendChild(todoItemElement);
});
return todoListElement;
}
}
- TodoItemView.js
TodoItemView.js
import { element } from "./html-util.js";
export class TodoItemView {
/**
* `todoItem`に対応するTodoアイテムのHTML要素を作成して返す
* @param {TodoItemModel} todoItem
* @param {function({id:number, completed: boolean})} onUpdateTodo チェックボックスの更新イベントリスナー
* @param {function({id:number})} onDeleteTodo 削除ボタンのクリックイベントリスナー
* @returns {Element}
*/
createElement(todoItem, { onUpdateTodo, onDeleteTodo }) {
const todoItemElement = todoItem.completed
? element`<li><input type="checkbox" class="checkbox" checked>
<s>${todoItem.title}</s>
<button class="delete">x</button>
</li>`
: element`<li><input type="checkbox" class="checkbox">
${todoItem.title}
<button class="delete">x</button>
</li>`;
const inputCheckboxElement = todoItemElement.querySelector(".checkbox");
inputCheckboxElement.addEventListener("change", () => {
// コールバック関数に変更
onUpdateTodo({
id: todoItem.id,
completed: !todoItem.completed
});
});
const deleteButtonElement = todoItemElement.querySelector(".delete");
deleteButtonElement.addEventListener("click", () => {
// コールバック関数に変更
onDeleteTodo({
id: todoItem.id
});
});
// 作成したTodoアイテムのHTML要素を返す
return todoItemElement;
}
}
Appのイベントリスナーを整理
App.jsのリスナー関数をhandleメソッドとして実装して、それを呼び出すように変更
- App.js
App.js
import { render } from "./view/html-util.js";
import { TodoListView } from "./view/TodoListView.js";
import { TodoItemModel } from "./model/TodoItemModel.js";
import { TodoListModel } from "./model/TodoListModel.js";
export class App {
#todoListView = new TodoListView();
#todoListModel = new TodoListModel([]);
/**
* Todoを追加するときに呼ばれるリスナー関数
* @param {string} title
*/
handleAdd(title) {
this.#todoListModel.addTodo(new TodoItemModel({ title, completed: false }));
}
/**
* Todoの状態を更新したときに呼ばれるリスナー関数
* @param {{ id:number, completed: boolean }}
*/
handleUpdate({ id, completed }) {
this.#todoListModel.updateTodo({ id, completed });
}
/**
* Todoを削除したときに呼ばれるリスナー関数
* @param {{ id: number }}
*/
handleDelete({ id }) {
this.#todoListModel.deleteTodo({ id });
}
mount() {
const formElement = document.querySelector("#js-form");
const inputElement = document.querySelector("#js-form-input");
const todoItemCountElement = document.querySelector("#js-todo-count");
const containerElement = document.querySelector("#js-todo-list");
this.#todoListModel.onChange(() => {
const todoItems = this.#todoListModel.getTodoItems();
const todoListElement = this.#todoListView.createElement(todoItems, {
// Appに定義したリスナー関数を呼び出す
onUpdateTodo: ({ id, completed }) => {
this.handleUpdate({ id, completed });
},
onDeleteTodo: ({ id }) => {
this.handleDelete({ id });
}
});
render(todoListElement, containerElement);
todoItemCountElement.textContent = `Todoアイテム数: ${this.#todoListModel.getTotalCount()}`;
});
formElement.addEventListener("submit", (event) => {
event.preventDefault();
this.handleAdd(inputElement.value);
inputElement.value = "";
});
}
}
Discussion