Knockout.js + Json Patch でモデルの差分検出/適用


最近手がけたプロジェクトではクライアントサイドとサーバーサイドのモデルを同期させるときの通信量を削減するために、JSON Patch RFC6902 を採用してみました。JSON Patch の簡単な説明とサーバーサイドの実装方法について、以前に紹介しましたが、今回はKnockout.jsを使ったクライアントサイド用の実装方法と利用パターンをご紹介したいと思います。


JSON Patch とは


簡単に前回の記事を振り返ると、JSON Patch は以下の2つのメイン機能を想定しています。

  • モデルに変更を適用するためのオペレーションの定義。
  • モデルに変更があったとき、その変更内容の検出と表記方法(オペレーションオブジェクト)。

一つのオペレーションオブジェクトの簡単な例です。

このオペレーションをモデルに適用すれば、モデルの age プロパティが 20 に設定されます。

逆にモデルの age プロパティが 20 に変わったら、変更前と変更後の差分の表記方法として、同じオペレーションオブジェクトが使えます。


利用パターン


変更の適用、変更の検出が可能になると、色々な利用パターンが考えられますが、今回のプロジェクトでは以下の機能を実装しました。

  • サーバーから JSON Patch データを受け取り、モデルを更新
  • モデルを更新するダイアログウインドウとメイン画面の間のモデルの差分の通信
  • モデルに対する変更の検出

具体例としてKnockout.js を元にした実装方法を紹介したいと思います。


実装方法


※ これから説明するコードはこちらの gist にまとまっています。
※ 試していただくための jsfiddle も用意しています。

キーポイント

実装の観点から、2つのキーポイントがあります。

  • プロパティに対する変更の適用と検出
  • モデル(のインスタンス)に対する変更の適用と検出

この2つのキーポイントの実装方法を順番で説明します。

プロパティに対する変更の適用と検出

変更の適用と検出を可能にするためには、プロパティが以下の3つの機能を持っている必要があります

機能概要 用途 サンプルコードで
の対応メソッド
変更前の値の保持と現在の値との比較 変更の検出 _getChanges
変更前の値を現在の値にリセット 変更の検出の結果を空にする _resetChanges
現在の値の変更 JSON Patch による変更の適用 _applyChanges
今回は、Knockout.js を使っているので、プロパティは Knockout.js の observable になっています。Knockout.js の extender を利用して、observable に上記の3つのメソッドを定義します。

extender を作成する前に、考慮しなければならない点が一つあります。observable が保持している値のタイプによって、_getChange_resetChange、_applyChange メソッドの実装が異なるので、以下の3つのタイプに合わせて、専用の extender を用意する必要があります。

observableの値のタイプ extender名
プリミティブ track
モデル trackObject
モデルの配列 trackObjectArray
track


trackObject


根本的に、プリミティブ用の extender の実装と同じですが、2つの細かい違いがあります。
  • JSON データから新規のインスタンスを生成するために、コンストラクタを使っていること
    コンストラクタは extender のパラメータとして渡されます。
  • 各メソッドが内部でそれぞれ koPatch.getChangeskoPatch.resetChangeskoPatch.applyChanges というメソッドを呼び出していること
    モデルに対する変更の適用と検出をするための専用のメソッドです。このメソッドの実装方法は"モデルに対する変更の適用と検出"で説明します。

trackObjectArray


モデルの配列の場合、以下の2つの要素を考慮しなければなりません。

  1. 配列のエレメントの追加、削除、置き換え
  2. エレメント自体に対する変更の処理
1. の処理を行うのに、Knockout.js のネイティブの配列変更監視機能を採用しています。エレメントの追加・削除・置き換えのときに、その変更内容が observableArrayChanges 変数に保持されます。

また、_getChanges_resetChanges_applyChangesが呼ばれたときに、配列の各エレメントに対して、それぞれkoPatch.getChangeskoPatch.resetChangeskoPatch.applyChanges を呼び出し、2. の処理を行います。

コード2、3、4で定義した extender を登録します。


モデルのサンプル

tracktrackObjecttrackObjectArray の extenders を利用して、モデルを作成します。

基本的に以下のようにモデルとプロパティの定義ができます。


しかし、直接 ko.observable(...).extend({ track: true }) シンタクスを使うと、エラーが起こりやすく、冗長な書き方になってしまうので、以下の3つヘルパーメソッドを使います。


そして以下のようにモデルを定義します。


※ 使われる環境、言語(CoffeeScript、TypeScript等)によって、モデルの定義方法を調整してください。

モデルに対する変更の適用と検出

プロパティに対しての変更の適用と検出の方法について説明しましたが、次はモデル自体に対して同じ処理を行うメソッドの実装方法を説明します。

同様なメソッドを実装します。
  • getChanges
  • resetChanges
  • applyChanges

上記メソッドは、パラメータとしてモデルを受け、モデルの各プロパティに対して、それぞれ  _getChanges、 _resetChanges、 _applyChange メソッドを再帰的に呼び出し、変更の適用や変更の検出を行います。


使い方のサンプル


コード9で定義したモデルを利用して、使い方のサンプルを紹介します。

このサンプルでは、ユーザーのプロフィールを編集し、DBに保存する操作をイメージします(実際のAJAXのやり取りは省略)。

上記のコードを実行すると、以下のアウトプットになります。


終わりに


モデルのフィールドが多く、そのまま送ると転送量が多くなるような場合に、JSON Patch を利用して転送量を減らすことができます。レスポンスタイムの短縮につながりますので、モバイル、デスクトップのウェブアプリケーションのユーザーエクスペリエンスの向上ができます。今回ご説明したように実装は簡単です。皆様もぜひ検討してみてください。

著者について
グリシン キリルは、11年前にロシアから来日し、2013年からエンラプトでウェブアプリケーションの開発に従事する。Functional Reactive Programmingに夢中で、クライアントサイドの開発を簡単に、より確かにするための研究を行う。

TypeScriptラムダ式のライブラリ(jQuery、Knockoutjs)での利用

前回、TypeScriptとthisのおさらい という記事で TypeScriptでラムダ式を使った場合の this の留意点について簡単におさらいしました。

実際の開発現場では、色々なライブラリと一緒に使った場合に考慮する事があるでしょう。 弊社が経験した TypeScript と組み合わせたライブラリ( jQuery、Knockoutjs )での考慮点を、簡単なサンプルとともに紹介していきたいと思います。

TypeScriptとthisのおさらい

今回はAltJsとして弊社がよく利用するTypeScriptにおけるthisについておさらいします。

基本的にはJavaScriptと変わらない、、、のですが。
TypeScriptとJavaScriptの違いとして、関数の定義方法によるところがあります。

  • アロー関数式
    • ()=>{} 
    • ラムダ [lambda]式でも通じるかと思います。
      • 当記事では「ラムダ式」と記載します。
  • 従来function式
    • function(){}

それぞれJavaScriptのfunctionと同じように使えます。


このラムダ式ですが、略記以外に重要なポイントがあります。
TypeScriptのラムダ式ではthisの扱いが異なるという所です。(ハンドブック参照)

Popular Posts