必要条件

この記事に必要な予備知識

JavaScriptの基礎知識。

ユーザーレベル

中級

原文 作成日: 2012/11/5
Getting Started with Knockout.js

Knockout.jsは、モデルデータに対して宣言によって要素をバインドできるようにするJavaScriptライブラリです。このモデルデータでは、UIとモデルの間で自動的に双方向の更新が行われます。Knockoutは簡単に使い始めることができ、他のライブラリやテクノロジーとも密接に統合されています。ここでは、使い始める際に知っておく必要があることについて説明します。

なぜKnockoutを使用するのか

複雑で動的なデータ駆動型のWebアプリケーションを開発することは、難しい作業になる可能性があります。ユーザーインターフェイスとその基になるデータとを常に同期させるには、通常、ユーザーが何かアクションを実行するたびに、または新しいデータが読み込まれるたびに、様々な要素とデータとの間で情報を仲介する多くのイベントハンドラーを作成する必要があります。

Knockoutの登場以前

Knockoutのメリットを示す簡単な方法として、Knockoutを使用する場合と使用しない場合で、シンプルなエディターの作成方法を比較してみます。このエディターには、アイテムの名前を表示するdivと、その名前を編集するためのinputがあるとします。


<div id="itemName"></div>
<input type="text" id="itemNameEdit" />

jQueryを使用して、アイテムのデータとこれらの要素とのインタラクションを次のように結び付けることができます。


var item = {
    id: 88,
    name: "Apple Pie"
};
$("#itemName").text(item.name);
$("#itemNameEdit").val(item.name).change(function() {
    item.name = $(this).val();
    $("#itemName").text(item.name);
});

このコードについては何も難しい点はありませんが、アプリケーションで管理する必要があるUIとデータの間での対話は多くなっていくので、この層のコードは増加し続け、やがてメンテナンスが難しくなる可能性があります。さらに、この種類のコードはこの特定のマークアップと密接に関連付けられているため、再利用も困難です。

データバインディングの追加

Knockoutによって、マークアップ内で宣言的なバインディングを使用して、UIとデータを容易に結び付けることができます。多くの場合、スタイル設定で使用される場合を除き、要素にIDやクラスは不要になります。Knockoutのバインディングは、個々の要素のdata-bind属性で指定されます。


<div data-bind="text: name"></div>
<input type="text" data-bind="value: name" />

要素のバインディングが指定されたら、ko.applyBindings関数を使用して、データを取り出して要素に適用するようにKnockoutに指示する必要があります。


var item = {
    id: 88,
    name: "Apple Pie"
};
ko.applyBindings(item);

この時点で、divによって「Apple Pie」と表示され、inputに対する編集内容はアイテムのnameプロパティに永続化されます。ただし、これらの更新によって、divが自動的に更新されるわけではありません。これらの更新が関係する機能に通知されるように強制するには、nameプロパティでKnockoutのコア機能の1つであるオブザーバブルを使用する必要があります。オブザーバブルを作成するために、nameプロパティをko.observableの呼び出しでラッピングする必要があります。


var item = {
    id: 88,
    name: ko.observable("Apple Pie")
};
ko.applyBindings(item);

これで、divは、nameオブザーバブルが変化するたびに更新されます。データバインディングは自動的にすべてのオブザーバブルにサブスクライブされ、更新が通知されるたびにそのDOM要素を適切に更新できます。プログラムによってオブザーバブルの変更をサブスクライブして、サーバーにデータをプッシュするなど、追加のロジックを実行することもできます。

MVVMパターンの使用

Knockoutでは、MVVM(Model-View-ViewModel)パターンを使用してユーザーインターフェイスを構築できます。このパターンは、複数の層を明確に分離することによって、複雑な機能をどのように管理できるかを示しています。

モデル

モデルは、アプリケーションのデータを表します。Knockoutでは、具体的にバックエンドサーバーとの間で行われるデータの仲介を処理するわけではなく、開発者がこの層の最適な実装方法を決定できます。また、JavaScriptコード内からデータにアクセスできる限り、Knockoutで特定のバックエンドテクノロジーが必要とされるわけでもありません。これを実現するための一般的な方法の例として、次のような方法があります。

  • サーバーに対してデータを送受信するためのAJAX要求を行う
  • ページのスクリプトブロックに直接データをシリアル化する
  • 必要に応じて、通常、外部のJavaScriptファイルから読み込まれる静的データを使用する
  • 状況に応じて、カスタムバインディングを使用してレンダリングされたコンテンツからビューモデルを構築する

ビュー

ビューはマークアップを表し、実際には、ビューモデルをレンダリングするためのテンプレートと考えることができます。堅牢なビューモデルでは、ビューモデルが公開する概念に対して単純な宣言的バインディングを使用するのに必要なものがすべてビューに含まれている必要があります。バインディングで適切な結果を得るために、複雑なロジックや式を含める必要があることが判明した場合、ビューをサポートするのに最適な方法でビューモデルが構造化されていないことを示しています。

ビューモデル

ビューモデルはアプリケーションの中心であり、データと、データを操作するために関連付けられたビヘイビアーを含むユーザーインターフェイスのコード表現です。ビューモデルの目的は、バインドするビューのわかりやすい構造を提供することです。モデルデータのフィルター、ソートおよび操作されたバージョンや、ユーザーインターフェイスでの処理に関連する潜在的な追加メタデータや概念を含めることができます。例えば、アイテムが表示可能であるかまたは編集可能であるかを追跡することはできますが、この情報をデータベースに永続化することはできません。

理想的なKnockoutアプリケーションでは、ビューモデルにバインドされているビューのDOM要素、セレクターまたはあらゆる直接的な情報への参照がビューモデルに含まれません。このような分離にはいくつかの利点があります。

  • ビューモデルは、UIを使用せずにそれ自体でのテストが容易です。
  • 一般的に、セレクターの破壊を心配することなく、マークアップをリファクタリングすることができます。バインディングが適切であることを確認する必要はありますが、移動している要素上で直接リストされます。
  • 複数のビューで1つのビューモデルを使用できます。一般的な例として、1つのアイテムのビューをレンダリングする場合と、アイテムのコレクションを処理する場合の比較があります。状況によって、同じビューモデルのコードを共有できる代替モバイルビューをレンダリングすることもできます。

Knockoutコアの構造

JavaScriptでは、本質的に、プロパティの値を設定しても、変更があったことはだれにも通知されません。このニーズに対応するために、Knockoutでは、サブスクリプションを追跡し、変更があったときに通知を実行することを目的とした構造体がいくつか作成されます。基になるデータが変更されると、バインディングがトリガーされ、DOM要素に適切な更新を行うことで応答します。

オブザーバブル

依存関係の追跡と変更の通知を容易にするKnockoutの基本的な構造体はオブザーバブルと呼ばれます。オブザーバブルは、ko.observable()を呼び出すことによって作成できます。オブザーバブルは、実際には、現在の値を内部でキャッシュする関数です。オブザーバブルの値を取得するには、引数を指定せずに、この関数を呼び出します。値を設定するには、新しい値を含む単一の引数をオブザーバブルに渡します。


this.name = ko.observable("Apple Pie");
alert(this.name()); //read the value
this.name("Pumpkin Pie"); //set the value

計算済みオブザーバブル

Knockoutでは、依存先が変更されたときに、常に最新の計算された値を維持する方法として、計算済みオブザーバブルも提供しています。


this.formattedName = ko.computed(function() {
    return this.id + " - " + this.name();
}, this);

計算済みオブザーバブルが作成されるときに、その現在の値がキャッシュされ、その依存先のいずれかが更新されたときにのみ再評価されます。計算済みオブザーバブルは、各再評価でアクセスする値を追跡することによって、動的にこれらの依存先を確認します。つまり、依存先の値が更新されたことを指定する必要はなく、必要なことは、依存先の対象が変更されたときに、評価ロジックを実行することだけです。

observableArray

多くのアプリケーションでは、1つまたは複数のデータコレクションを処理します。Knockoutには、アイテムが追加または削除されたことをUIで認識するために必要なデータの配列をサポートするobservableArrayが含まれています。ko.observableArrayは、通常の配列操作を処理する追加の関数を含むオブザーバブルです。observableArrayを操作すると、基になる配列が更新され、すべてのサブスクライバーに配列の変更が通知されます。


this.items = ko.observableArray([
    { id: 1, name: "Apple Pie" },
    { id: 2, name: "Pumpkin Pie" },
    { id: 3, name: "Blueberry Torte" }
]);
this.items.push({ id: 4, "Strawberry Shortcake" });

observableArrayは、各アイテムの個々のプロパティではなく、配列内のアイテムに対する変更を追跡することに注意してください。前の例で、アプリケーションがアイテムの名前の変更に応答する必要がある場合は、nameプロパティもオブザーバブルにする必要があります。

組み込みのバインディング

バインディングは、マークアップとビューモデルを結び付ける魔法のようなものです。Knockoutには、一般的な用途のバインディングが含まれており、カスタム機能によって拡張することもできます。表1-3に、Knockoutで用意されているバインディングを示します。

表1.外観と一般的なバインディング

バインディング

説明

visible

visibleバインディングでは、渡された値に基づいて、要素の表示と非表示を切り替えることができます。

<div data-bind="visible: hasError">An error has occurred</div>

text

textバインディングは、渡された値を使用して、要素の内容を設定します。

<div data-bind="text: message"></div>

html

htmlバインディングは、渡されたマークアップを使用して、要素の子を設定します。

<div data-bind="html: markup"></div>

css

cssバインディングは、要素上の1つまたは複数のCSSクラスを切り替えます。

<div data-bind="css: { error: hasError, required: isRequired }">content</div>

style

styleバインディングは、要素にスタイル値を追加します。

<div data-bind="style: { color: messageColor, backgroundColor: backColor }">content</div>

attr

attrバインディングは、要素上の1つまたは複数の属性の値を設定します。

<div data-bind="attr: { title: itemDescription, id: itemId }">content</div>

表2. フォームフィールドのバインディング

バインディング

説明

click

clickバインディングは、要素がクリックされたときにハンドラーを実行します。

<button data-bind="click: addItem">Add Item</button>

event

eventバインディングは、指定されたイベントの要素にハンドラーを追加します。

<div data-bind="event: { mouseover: showHelp, mouseout: hideHelp }">content</div>

submit

submitバインディングによって、フォームが送信されたときにハンドラーを実行できます。

<form data-bind="submit: saveData">…</form>

value

valueバインディングは、フィールドの値とビューモデルの値の双方向のバインディングを実現します。

<input data-bind="value: name" />

enable

enableバインディングは、値が渡されたときに、フォーム要素を有効にするかどうかを制御します。

<input data-bind="enable: isEditable, value: name" />

disable

disableバインディングはenableバインディングと同じ機能を提供しますが、渡された値の逆を使用します。

<input data-bind="disable: isReadOnly, value: name" />

hasfocus

hasfocusバインディングは、要素のフォーカスの状態を追跡し、値がtrueに設定されたときに、フィールドにフォーカスを与えることを試行します。

<input data-bind="hasfocus: nameFocused, value: name" />

checked

checkboxバインディングは、ラジオボタンやチェックボックスに対してバインディングするために使用されます。これによって、チェックボックスがオンになっているかどうかや、現在選択されているラジオボタンの値を追跡できます。配列に対してバインディングされている場合は、現在オンになっているすべての値を追跡できます。

<input type="checkbox" data-bind="checked: isActive" />

options

optionsバインディングは、select要素のオプションを設定するために使用されます。値の表示および格納の方法をカスタマイズする、optionsTextoptionsValueおよびoptionsCaptionオプションが含まれています。

<select data-bind="options: choices, value: name"></select>

selectedOptions

selectedOptionsバインディングは、複数選択が可能なselect要素で現在選択されているアイテムを追跡します。

<select data-bind="options: availableFilters, selectedOptions: selectedFilters" size="10" multiple="true"></select>

表3.制御フローとテンプレートのバインディング

バインディング

説明

if

ifバインディングは、要素の子がレンダリングおよびバインディングされるかどうかを決定します。バインディングされた値が変更されたときには、子要素のコピーを取得して、処理の「テンプレート」として使用します。

<div data-bind="if: detailsLoaded">
    <div data-bind="text: content"></div>
</div>

ifnot

ifnotバインディングはifバインディングと同じ機能を提供しますが、渡された値の逆を使用して、要素をレンダリングおよびバインディングするべきであるかどうかを決定します。

<div data-bind="ifnot: hideDetails">
    <div data-bind="text: content"></div>
</div>

with

withバインディングは、渡された値をデータコンテキストとして使用して、子要素をバインディングします。値がnull/undefined/falseである場合、子はレンダリングされません。また、バインディングされた値が変更されたときには、子要素のコピーを保持して、処理の「テンプレート」として使用します。

<div data-bind="with: details">
    <div data-bind="text: title"></div>
    <div data-bind="text: content"></div>
</div>

foreach

foreachバインディングでは、子要素を「テンプレート」として使用して、渡された配列内の各アイテムを反復処理します。

<ul data-bind="foreach: items">
   <li data-bind="text: name"></li>
</ul>

template

templateバインディングは、ififnotwithおよびforeachバインディングの基になる機能を提供しますが、名前付きのテンプレートを指定して、複数回再利用することもできます。

<!—just passing a named template -->
<div data-bind="template: ‘itemsTmpl‘"></div>
<script id="itemTmpl" type="text/html">
   <div data-bind="text: name"></div>
</script>
<!—controlling the data that is bound by the template -->
<div data-bind="template: { name: ‘itemTmpl‘, data: currentItem }"></div>
<!—iterating through an array of items -->
<div data-bind="template: { name: ‘itemTmpl‘, foreach: items }"></div>


Knockoutでの拡張性

Knockoutの方針の1つとして、その基本的な機能に固執し、コア機能以外の領域については拡張ポイントを提供することがあります。これによって、Knockoutを開発者の好みのライブラリやテクノロジーと柔軟に統合できます。Knockoutを最大限に活用するには、Knockoutで提供されている様々な拡張ポイントを熟知しておく必要があります。

カスタムバインディング

Knockoutでは、独自のバインディングを容易に作成することができ、一般的には、これば最もよく使用される拡張ポイントになります。組み込みバインディングでは処理できない方法でデータとDOM要素の両方を処理するコードを記述することが必要になった場合は、カスタムバインディングの作成を検討できます。

すべてのバインディング(カスタムおよび組み込み)は、ko.bindingHandlersオブジェクトに含まれています。各バインディングは、バインディングが最初に実行されたときにのみ実行されるinit関数、依存先のいずれかが変更されるたびに実行されるupdate関数、またはその両方を実装することを選択できるオブジェクトです。

既存のtextバインディングをラッピングし、テキストが変更されたときにテキストをフェードするカスタムバインディングの例を以下に示します。


ko.bindingHandlers.fadeInText = {
    update: function(element, valueAccessor, allBindings, data, context) {
        $(element).hide();
        ko.bindingHandlers.text.update(element, valueAccessor);
        $(element).fadeIn();
    }
};

カスタムバインディングについて詳しくは、カスタムバインディングに関するドキュメントと、カスタムバインディングの一般的な使用例について説明したこちらのカスタムバインディングに関するブログの記事を参照してください。

Knockoutの構造体の拡張

Knockoutによって、前に説明したコア構造体を拡張することもできます。これによって、ビューモデルのコードをクリーンで簡潔なまま維持しながら、再利用可能な機能を作成できるようになります。

Knockoutのコアの各型に共通な関数はすべて、その型のfnオブジェクト(ko.observable.fnなど)に含まれています。したがって、ko.observable.fnに追加した関数は、すべてのオブザーバブルで利用できます。型の階層について詳しくは、Knockout公式サイトの「Adding custom functions using "fn"」(「fn」を使用したカスタム関数の追加)を参照してください。

オブザーバブルにeditValueサブオブザーバブルを追加するeditable拡張機能を作成する例を以下に示します。これによって、一時的な値に対してバインディングし、accept関数を呼び出すことによって、実際のオブザーバブルにコミットすることができます。


ko.observable.fn.editable = function() {
    //create a sub-observable that we can bind against for editing
    this.editValue = ko.observable(this());
    //when accept is called, commit the temp value to our real value
    this.accept = function() {
        this(this.editValue());
    }.bind(this);
    //return the observable itself (this) to support chaining
    return this;
};

次のようなオブザーバブルを作成するときに、この拡張機能を使用できます。


this.name = ko.observable("Bob").editable();

マークアップでは、次のようにバインディングすることができます。


<div data-bind="text: name"></div>
<input data-bind="value: name.editValue" />
<button data-bind="click: name.accept">Accept</button>

Knockoutのコアの各型のfnオブジェクトを拡張すると、Knockoutの機能を拡張できる、再利用可能な概念を作成するのに便利です。fnオブジェクト以外に、Knockoutではextendersと呼ばれる同様のアプローチもサポートしています。extendersについては、Knockoutのドキュメントの「Using extenders to augment observables」(extendersを使用したオブザーバブルの強化)を参照してください。

その他の拡張ポイント

Knockoutには、他にも注目すべき拡張ポイントがあります。

  • テンプレートエンジン:Knockoutでは、templateバインディングで代替テンプレートを使用できます。用意されているベーステンプレートエンジンを拡張することによって、データとテンプレートを実行して結果のマークアップを決定する方法をカスタマイズすることができます。
  • テンプレートソース:Knockoutでテンプレートの内容を決定する方法をカスタマイズできます。これによって、外部ソースからテンプレートを読み込んだり、テンプレートをマークアップの外部に格納したりすることができます。
  • バインディングプロバイダー:Knockoutで、バインディングを持つ要素やバインディングの解析方法を確認する方法をオーバーライドすることができます。つまり、data-bind属性を独自の構造に置き換えることができます。コード内でバインディングを指定したり、マークアップからバインディングをキー入力したりできるようにする代替バインディングプロバイダーについては、筆者が作成したknockout-classBindingProvider GitHubプロジェクトと、バインディングプロバイダーの作成方法について説明したKnockout 1.3プレビューに関するブログの記事を参照してください。

次のステップ

Knockoutの基本事項が理解できたら、次のステップとして以下を参照してください。最初に、Knockoutのオンラインチュートリアルを参照することをお勧めします。このチュートリアルでは、基本的な項目からより高度な項目まで、一般的な様々なシナリオについて解説しています。さらに、詳細なドキュメントはKnockout公式サイトに用意されており、Knockout GitHubプロジェクトからソースコードを入手することもできます。

現在、新しいコメントシステムに移行中であるため、コメントの投稿は受け付けておりません。しばらくの間、ご意見やご感想をお寄せいただく場合は、フィードバックフォームをご利用ください。ご迷惑をおかけしますが、どうぞよろしくお願い申し上げます。