🔖

JavaScript PrimerのTodoアプリ作成でJS復習(アイテム追加まで)

2023/12/30に公開

記事概要

JSの復習として、JavaScript PrimerのTodoアプリ作成をやってみて、結構いい復習になったので紹介
https://jsprimer.net/

対象読者

JavaScriptの基本的な構文は知っている初学者向けです。

復習内容

コピペでTodoアプリが完成してしまうからこそ、一つ一つの意味理解に割くことができます!

エントリーポイントを作る理由

まず、この教材ではindex.jsというエントリーポイントからモジュールを読み込んでいきます。
以下、その意味の例です。(GPTの力活用)

実行の起点を統一する
複数のモジュールを使用する場合、どのモジュールから実行を開始するかを明確にする必要があります。エントリーポイントはその起点となります。これにより、コードの読みやすさが向上し、プログラム全体の構造が理解しやすくなります。

初期化と設定
エントリーポイントはアプリケーションの初期化や設定のためのコードを含むことが一般的です。例えば、必要なライブラリの読み込み、グローバル変数の初期化、イベントリスナーの設定などが含まれます。

依存関係の解決
エントリーポイントは他のモジュールを読み込み、それらの依存関係を解決します。各モジュールが独立していても、エントリーポイントを通じて相互に連携することが可能です。

読み込み順序の管理
JavaScriptのモジュールは依存関係が解決された順に読み込まれます。エントリーポイントを介して依存関係を一元管理することで、適切な順序でモジュールが読み込まれ、正しく動作することが保証されます。

コード分割と効率的な読み込み
エントリーポイントを通じて必要なモジュールだけを読み込むことで、不要なコードの読み込みを避けることができ、ページの読み込み速度を向上させることができます。

Todoアイテムの作成

コピペ+理解を1時間くらいでできます。なんかはよ終わったし、記事でも書ことなってます()

  1. index.html
    id属性にJSで使う部分が割り当てられている
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Todo App</title>
    <!-- 1. CSSファイルを読み込み -->
    <link
      href="https://jsprimer.net/use-case/todoapp/final/final/index.css"
      rel="stylesheet"
    />
  </head>
  <body>
    <!-- 2. class属性をCSSのために指定 -->
    <div class="todoapp">
      <!-- 3. id属性をJavaScriptのために指定 -->
      <form id="js-form">
        <input
          id="js-form-input"
          class="new-todo"
          type="text"
          placeholder="What need to be done?"
          autocomplete="off"
        />
      </form>
      <!-- 4. TodoアプリのメインとなるTodoリスト -->
      <div id="js-todo-list" class="todo-list">
        <!-- 動的に更新されるTodoリスト -->
      </div>
      <footer class="footer">
        <!-- 5. Todoアイテム数の表示 -->
        <span id="js-todo-count">Todoアイテム数: 0</span>
      </footer>
    </div>
    <script src="./index.js" type="module"></script>
  </body>
</html>
  1. index.js
    エントリーポイントであり、モジュールを読み込む
import { App } from "./src/App.js";
const app = new App();
app.mount();
  1. App.js
    todoアイテムを作成し、#js-todo-listに表示する処理を記載
import { element, render } from "./view/html-util.js";

export class App {
    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");
        // TodoリストをまとめるList要素
        const todoListElement = element`<ul></ul>`;
        // Todoアイテム数
        let todoItemCount = 0;
        formElement.addEventListener("submit", (event) => {
            // 本来のsubmitイベントの動作を止める
            event.preventDefault();
            // 追加するTodoアイテムの要素(li要素)を作成する
            const todoItemElement = element`<li>${inputElement.value}</li>`;
            // TodoアイテムをtodoListElementに追加する
            todoListElement.appendChild(todoItemElement);
            // コンテナ要素の中身をTodoリストをまとめるList要素で上書きする
            render(todoListElement, containerElement);
            // Todoアイテム数を+1し、表示されてるテキストを更新する
            todoItemCount += 1;
            todoItemCountElement.textContent = `Todoアイテム数: ${todoItemCount}`;
            // 入力欄を空文字列にしてリセットする
            inputElement.value = "";
        });
    }
}
  1. html-util.js
    App.jsでインポートしているelement, renderの内容

escapeSpecialChars(str)で文字列がHTMLコンテキストで安全に表示されるようにする。

htmlToElement(html)で<template> 要素を使用し、スクリプトによる副作用を防ぎつつ、HTML文字列を解釈してDOMツリーに変換。

element(strings, ...values)でテンプレートリテラルを受け取り、HTML文字列を生成してから htmlToElement 関数を使ってDOM要素に変換。。また、escapeSpecialChars 関数を使用して特殊文字をエスケープ。

render(bodyElement, containerElement)でbodyElement を containerElement の中に描画。具体的には、containerElement の中身を空にし、その後に bodyElement を追加。これにより、コンテナ要素の中身が指定された要素で上書きされる。

export function escapeSpecialChars(str) {
  return str
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
}

/**
* HTML文字列からHTML要素を作成して返す
* @param {string} html 
*/
export function htmlToElement(html) {
  const template = document.createElement("template");
  template.innerHTML = html;
  return template.content.firstElementChild;
}

/**
* HTML文字列からDOM Nodeを作成して返すタグ関数
* @return {Element}
*/
export function element(strings, ...values) {
  const htmlString = strings.reduce((result, str, i) => {
      const value = values[i - 1];
      if (typeof value === "string") {
          return result + escapeSpecialChars(value) + str;
      } else {
          return result + String(value) + str;
      }
  });
  return htmlToElement(htmlString);
}

/**
* コンテナ要素の中身をbodyElementで上書きする
* @param {Element} bodyElement コンテナ要素の中身となる要素
* @param {Element} containerElement コンテナ要素
*/
export function render(bodyElement, containerElement) {
  // containerElementの中身を空にする
  containerElement.innerHTML = "";
  // containerElementの直下にbodyElementを追加する
  containerElement.appendChild(bodyElement);
}

ブラウザの表示

submitしたものがリストに追加されていく

次回の修正点

  • DOM更新が直接される
  • 追加したTodoアイテム要素を識別できない
    この修正を実施

Discussion