GitBookjs-primerイベントとモデルTodoアイテムを追加する機能を実装しましたが、イベントを受け取って直接DOMを更新する方法には柔軟性がないという問題があります。「Todoアイテムの更新」という機能を実装するには、追加したTodoアイテム要素を識別する方法が必要です。具体的には、Todoアイテムごとにid属性などのユニークな識別子がないため、特定のアイテムを指定して更新や削除をする機能が実装できません。このセクションでは、まずどのような点で柔軟性の問題が起きやすいのかを見ていきます。柔軟性や識別子の問題を解決するためにモデルという概念を導入し、「Todoアイテムの追加」の機能をリファクタリングしていきます。「Todoアイテムの追加を実装する」では、操作した結果発生したイベントという入力に対して、DOM(表示)を直接更新していました。そのため、TodoリストにTodoアイテムが何個あるか、どのようなアイテムがあるかという状態がDOM上にしか存在しないことになります。この場合にTodoアイテムの状態を更新するには、HTML要素にTodoアイテムの情報(タイトルや識別子となるidなど)をすべて埋め込む必要があります。HTML要素は文字列しか扱えないため、Todoアイテムのデータを文字列にしないといけないという制限が発生します。1つの操作に対して複数の箇所の表示が更新されることもあります。今回のTodoアプリでもTodoリスト(#js-todo-list)とTodoアイテム数(#js-todo-count)の2箇所を更新する必要があります。次の表に操作に対して更新する表示をまとめてみます。Todoリスト(#js-todo-list)にTodoアイテム要素を作成して子要素として追加。Todoリスト(#js-todo-list)にある指定したTodoアイテム要素を削除。合わせてTodoアイテム数(#js-todo-count)を更新1つの操作に対する表示の更新箇所が増えるほど、操作に対する処理(リスナーの処理)が複雑化していくことが予想できます。ここでは、次の2つの問題が見つかりました。Todoリストの状態がDOM上にしか存在しないため、状態をすべてDOM上に文字列で埋め込まないといけない操作に対して更新する表示箇所が増えてくると、表示の処理が複雑化するモデルを導入するこの問題を避けるために、Todoアイテムという情報をJavaScriptクラスとしてモデル化します。ここでのモデルとはTodoアイテムやTodoリストなどのモノの状態や操作方法を定義したオブジェクトという意味です。クラスでは操作方法はメソッドとして実装し、状態はインスタンスのプロパティで管理できるため、今回はクラスでモデルを表現します。Todoリストを表現するモデルとしてTodoListModelクラスを考えます。TodoリストにはTodoアイテムを追加できるので、TodoListModel#addItemというメソッドがあると良さそうです。Todoリストからアイテムの一覧を取得できる必要もあるので、TodoListModel#getAllItemsというメソッドも必要そうです。このようにTodoリストをクラスで表現する際に、オブジェクトがどのような処理や状態を持つかを考えて実装します。このようにモデルを考えた後、先ほどの操作と表示の間にモデルを入れることを考えてみます。「フォームを入力して送信」という操作をした場合には、TodoListModel(Todoリスト)に対してTodoItemModel(Todoアイテム)を追加します。TodoListModelからTodoアイテムの一覧を取得し、それを元にDOMを組み立て、表示を更新します。先ほどの表にモデルを入れてみます。操作に対するモデルの処理はさまざまですが、操作に対する表示の処理はどの場合も同じになります。これは表示箇所が増えた場合でも表示の処理の複雑さが一定に保てることを意味しています。TodoListModelを元に表示を更新この表を元に改めて先ほどの問題点を見ていきましょう。Todoリストの状態がDOM上にしか存在しないため、状態をすべてDOM上に文字列で埋め込まないといけないモデルであるクラスのインスタンスを参照すれば、Todoアイテムの情報が手に入ります。モデルはただのJavaScriptクラスであるため、文字列ではない情報も保持できます。そのため、DOMにすべての情報を埋め込む必要はありません。操作に対して更新する表示箇所が増えてくると、表示の処理が複雑化する表示はモデルの状態を元にしてHTML要素を作成し、表示を更新します。モデルの状態が変化していなければ、表示は変わらなくても問題ありません。そのため操作したタイミングではなく、モデルの状態が変化したタイミングで表示を更新すればよいはずです。「TodoListModelというモデルの状態が変化」したから表示を更新すればいいはずです。そのためには、TodoListModelというモデルの状態が変化したことを表示側から知る必要があります。ここで再び出てくるのがイベントです。モデルの変化を伝えるイベントフォームを送信したらform要素からsubmitイベントが発生します。これと同じようにTodoListModelの状態が変化したら自分自身へchangeイベントを発生(ディスパッチ)させます。表示側はそのイベントをリッスンしてイベントが発生したら表示を更新すればよいはずです。TodoListModelの状態の変化とは、「TodoListModelに新しいTodoItemModelが追加される」などが該当します。先ほどの表の「モデルの処理」は何かしら状態が変化しているので、表示を更新する必要があるわけです。APIのイベントの仕組みをモデルでも利用できれば、モデルが更新されたら表示を更新する仕組みを作れそうです。Eventsと呼ばれるイベントの仕組みが利用できます。Node.jsでは、eventsと呼ばれる組み込みのモジュールで同様のイベントの仕組みが利用できます。実行環境が提供するイベントの仕組みを利用すると簡単ですが、ここではイベントの仕組みを理解するために、イベントのディスパッチとリッスンする機能を持つクラスを作ってみましょう。難しく聞こえますが、今まで学んだクラスやコールバック関数などを使えば実現できます。EventEmitterイベントの仕組みとは「イベントをディスパッチする側」と「イベントをリッスンする側」の2つの面から成り立ちます。場合によっては自分自身へイベントをディスパッチし、自分自身でイベントをリッスンすることもあります。このイベントの仕組みを言い換えると「イベントをディスパッチした(イベントを発生させた)ときにイベントをリッスンしているコールバック関数(イベントリスナー)を呼び出す」となります。モデルが更新されたら表示を更新するには「TodoListModelが更新されたときに指定したコールバック関数を呼び出すクラス」を作れば目的は達成できます。「TodoListModelが更新されたとき」というのはとても具体的な処理であるため、モデルを増やすたびに同じ処理をそれぞれのモデルへ実装するのは大変です。そのため、先ほどのイベントの仕組みを持った概念としてEventEmitterというクラスを作成します。TodoListModelは作成したEventEmitterを継承することでイベントの仕組みを導入していきます。値を更新したとき、登録されているコールバック関数を呼び出すクラスまずは、親クラスとなるEventEmitterを作成していきます。EventEmitterはイベントの仕組みで書いたディスパッチ側とリッスン側の機能を持ったクラスとなります。addEventListenerメソッドは、指定したイベント名に任意のコールバック関数を登録できるこれによって、emitメソッドを呼び出すと指定したイベントに関係する登録済みのコールバック関数を呼び出せます。このようなパターンはObserverパターンとも呼ばれ、ブラウザやNode.jsなど多くの実行環境に類似するAPIが存在します。次のようにsrc/EventEmitter.jsへEventEmitterクラスを定義します。}}このEventEmitterでは次のようにイベントのリッスンとイベントのディスパッチの機能が利用できます。リッスン側はaddEventListenerメソッドでイベントの種類(type)に対するイベントリスナー(listener)を登録します。ディスパッチ側はemitメソッドでイベントをディスパッチし、イベントリスナーを呼び出します。次のコードでは、addEventListenerメソッドでtest-eventイベントに対して2つのイベントリスナーを登録しています。そのため、emitメソッドでtest-eventイベントをディスパッチすると、登録済みのイベントリスナーが呼び出されています。"Two!"EventEmitterを継承したTodoListモデル次は作成したEventEmitterクラスを継承したTodoListModelクラスを作成しています。src/model/ディレクトリを新たに作成し、このディレクトリに各モデルクラスを実装したファイルを作成します。作成するモデルは、Todoリストを表現するTodoListModelと各Todoアイテムを表現するTodoItemModelです。TodoListModelが複数のTodoItemModelを保持することでTodoリストを表現することになります。Todoアイテムを表現するモデルまずはTodoItemModelをsrc/model/TodoItemModel.jsというファイル名で作成します。TodoItemModelクラスは各Todoアイテムに必要な情報を定義します。各Todoアイテムにはタイトル(title)、アイテムの完了状態(completed)、アイテムごとにユニークな識別子(id)を持たせます。ただのデータの集合であるため、クラスではなくオブジェクトでも問題はありませんが、今回はクラスとして作成します。次のようにsrc/model/TodoItemModel.jsへTodoItemModelクラスを定義します。}}次のコードではTodoItemModelクラスはインスタンス化でき、それぞれのidが自動的に異なる値となっていることが確認できます。このidは後ほど特定のTodoアイテムを指定して更新する処理のときに、アイテムを区別する識別子として利用します。true次にTodoListModelをsrc/model/TodoListModel.jsというファイル名で作成します。TodoListModelクラスは、先ほど作成したEventEmitterクラスを継承します。TodoListModelクラスはTodoItemModelの配列を保持し、新しいTodoアイテムを追加する際はその配列に追加します。このときTodoListModelの状態が変更したことを通知するために自分自身へchangeイベントをディスパッチします。状態が変更されたときに呼ぶ。}}次のコードはTodoListModelクラスのインスタンスに対して、新しいTodoItemModelを追加するサンプルコードです。TodoListModel#addTodoメソッドで新しいTodoアイテムを追加したときに、TodoListModel#onChangeで登録したイベントリスナーが呼び出されます。1これでTodoリストに必要なそれぞれのモデルクラスが作成できました。次はこれらのモデルを使って、表示の更新をしてみましょう。モデルを使って表示を更新する先ほど作成したTodoListModelとTodoItemModelクラスを使って、「Todoアイテムの追加」を書き直してみます。前回のコードでは、フォームを送信すると直接DOMへ要素を追加していました。今回のコードでは、フォームを送信するとTodoListModelへTodoItemModelを追加します。そのリスナー関数内でDOM(表示)を更新します。書き換え後のApp.jsを見ていきます。}}変更後のApp.jsでは大きく分けて3つの部分が変更されているので、順番に見ていきます。作成したTodoListModelとTodoItemModelをインポートしています。"./model/TodoItemModel.js";そして、Appクラスのコンストラクタ内でTodoListModelを初期化しています。このTodoアプリでは開始時にTodoリストの中身が空の状態で開始されるのに合わせるためです。TodoListModelの状態が更新されたら表示を更新するmountメソッド内でTodoListModelが更新されたら表示を更新するという処理を実装します。TodoListModel#onChangeで登録したリスナー関数は、TodoListModelの状態が更新されたら呼び出されます。このリスナー関数内ではTodoListModel#getTodoItemsでTodoアイテムを取得しています。アイテム一覧から次のようなリスト要素(todoListElement)を作成しています。2のタイトル</li></ul>この作成したtodoListElement要素を、前回作成したhtml-util.jsのrender関数を使ってcontainerElementの中身に上書きしています。アイテム数はTodoListModel#getTotalCountメソッドで取得できるため、アイテム数を管理していたtodoItemCountという変数は削除できます。フォームを送信したら、新しいTodoItemを追加する前回のコードでは、フォームを送信(submit)すると直接DOMへ要素を追加していました。今回のコードでは、TodoListModelの状態が更新されたら表示を更新する仕組みがすでにできています。そのため、submitイベントのリスナー関数内ではTodoListModelに対して新しいTodoItemModelを追加するだけで表示が更新されます。DOMへappendChildしていた部分をTodoListModel#addTodoメソッドを使ってモデルを更新する処理へ置き換えるだけです。まとめ今回のセクションでは、前セクションの「Todoアイテムの追加を実装する」をモデルとイベントの仕組みを使うようにリファクタリングしました。コード量は増えましたが、次に実装する「Todoアイテムの更新」や「Todoアイテムの削除」も同様の仕組みで実装できます。前回のセクションのように操作に対してDOMを直接更新した場合、追加は簡単ですが既存の要素を指定する必要がある更新や削除は難しくなります。次のセクションでは、残りの機能である「Todoアイテムの更新」や「Todoアイテムの削除」を実装していきます。このセクションのチェックリスト直接DOMを更新する問題について理解したEventEmitterクラスでイベントの仕組みを実装したTodoリストとTodoアイテムをモデルとして実装したTodoListModelをEventEmitterクラスを継承して実装したTodoアイテムの追加の機能をモデルを使ってリファクタリングしたここまでのTodoアプリは次のURLで確認できます。https://jsprimer.net/use-case/todoapp/event-model/event-emitter/。