Polymerでは、要素のプロパティの変更を監視(observe)し、データ変更に基づき様々なアクションを実行することができます。これらのアクションや、プロパティエフェクト(property effects)には、次のものがあります。
-
オブザーバー(observers):データの変更時に呼び出されるコールバック
-
算出プロパティ(computed properties):他のプロパティに基づき算出され、入力データが変更されたときに再計算が行われる仮想的なプロパティ。
-
データバインディング(data binding):データが変更されたとき、DOMノードのプロパティまたは属性、テキストコンテンツをアップデートするアノテーション。
各Polymer要素は、独自のデータモデルとローカルDOM要素を管理します。要素のモデルとは、要素のプロパティです。データバインディングは、要素のモデルをローカルDOM内の要素とリンクします。
非常にシンプルな要素について考えてみましょう。:
<dom-module id="name-card">
<template>
<div>[[name.first]] [[name.last]]</div>
</template>
<script>
class NameCard extends Polymer.Element {
static get is() {return 'name-card';}
}
customElements.define(NameCard.is, NameCard);
</script>
</dom-module>
-
<name-card>
要素は、JavaScriptオブジェクトを参照するname
プロパティを持っている。 -
<name-card>
要素は、<div>
を一つだけ持ったローカルDOMのホストになる。 - テンプレート内のデータバインディングは、Javascriptオブジェクトを
<div>
要素にリンクする。
データシステムは、オブジェクトではなくパスに基づいており、パスはホスト要素に関連するプロパティやサブプロパティを表します。例えば、<name-card>
要素は、パスname.first
とパスname.last
に対するデータバインディングを持っています。仮に、<name-card>
要素がname
プロパティとして次のオブジェクトを持っている場合:
{
first: 'Lizzy',
last: 'Bennet'
}
パスname
は要素のname
プロパティ(オブジェクト)を参照します。パスname.first
とパスname.last
はオブジェクトのプロパティを参照します。
-
name
プロパティはJavascriptのオブジェクトを参照します。 - パス
name
はそのオブジェクト自身を参照します。パスname.first
はサブプロパティfirst
(文字列Lizzy
)を参照します。
Polymerは自動ですべてのデータ変更を検出するわけではありません。プロパティエフェクトは、プロパティまたはサブプロパティへ監視可能(obsevable)な変更があった場合に生じます。
**なぜパスを使うのでしょうか?**パスや監視可能(obsevable)な変更は、初めは少し奇妙にみえるかもしれません。しかし、これらによって、非常にハイパフォーマンスなデータバインディングシステムが実現されています。監視可能な変更が発生した場合、PolymerはDOM内で、その変更によって影響を受ける箇所にだけ変更を加えることができます。
要約:
- 単一のJavaScriptオブジェクトや配列は複数の要素から参照するこができますが、パスは常に特定の要素に関連づけられます。
- あるパスに対して監視可能(observable)な変更があると、バインドされた値の更新やオブザーバーの呼び出しといったプロパティエフェクトを発生させます。
- データバインディングは、異なる要素上のパス間にコネクションを確立します。
監視可能(observable)な変更
監視可能な変更とは、Polymerがパスに関連付けることができるデータの変更です。以下のような変更については自動的に監視することができます。:
-
要素のプロパティに直接設定する。
this.owner = 'Jane';
もし、あるプロパティに関連したプロパティエフェクトが発生した場合(オブザーバー、算出プロパティやデータバインディングなど)、Polymerはプロパティエフェクトを自動的に発生させるプロパティに対してsetterメソッドを作成します。
-
双方向データバインディングを使用して要素のサブプロパティを設定する。
<local-dom-child name="{{hostProperty.subProperty}}"></local-dom-child>
データバインディングシステムによってもたらされた変更は、自動的に伝播されます。この例で言えば、
<local-dom-child>
要素が自身のname
プロパティに変更を加えると、パスhostProperty.subProperty
に変更が行われたように、その変更をホストに向けて上に伝播します。
監視不能(unobservable)な変更
オブジェクトまたは配列を命令的に変更した場合は、監視することができません。例えば、以下のような場合です。:
-
オブジェクトのサブプロパティを設定:
// unobservable subproperty change this.address.street = 'Elm Street';
ここでサブプロパティ
address.street
を変更しても、address
のsetterメソッドが呼び出されることはないので、自動的に監視することはできません。 -
配列の変更:
// unobservable change using native Array.push this.users.push({ name: 'Maturin});
配列を変更しても
users
のsetterメソッドが呼び出されることはないので、自動的に監視することはできません。
どちらの場合も、変更を監視できるようにするには、Polymerの用意したメソッドを利用する必要があります。
オブジェクトと配列の変更を監視可能に
Polymerは、サブプロパティや配列を監視できるようにするメソッドを用意しています。
// mutate an object observably
this.set('address.street', 'Half Moon Street');
// mutate an array observably
this.push('users', { name: 'Maturin'});
特定のケースにおいては、Polymerのメソッドを利用してオブジェクトや配列を変更することができません。(サードパーティのライブラリを使用している場合など)。この場合には、notifyPathメソッドやnotifySplicesメソッドを使用することで、すでに発生した変更を要素に通知できます。
// Notify Polymer that the value has changed
this.notifyPath('address.street');
notifyPath
メソッドやnotifySplices
メソッドを呼び出すと、あたかも変更が発生したかのように、要素は適切なプロパティエフェクトを作用させます。
set
やnotifyPath
を呼び出す際は、変更された正確なパスを使用する必要があります。例えば、address
オブジェクト自体は変更されていないのに、this.notifyPath('address')
の呼び出しを行なった場合、address.street
の変更は検出されません。これは、Polymerがオブジェクトの等価性からオブジェクトと配列にダーティチェックを行うためです。指定されたパスの値が変更されていなければ、プロパティエフェクトは一切生じることがありません。
ほとんどの場合、オブジェクトの一つ以上のプロパティが変更された場合、または配列内の一つ以上のアイテムが変更された場合、オブジェクトや配列を複製(cloning)することで、Polymerによるダーティチェックを強制的に回避させることができます。
// Shallow clone array
this.addresses.push(address1);
this.addresses.push(address2)
this.addresses = this.addresses.slice();
多層的な(multiple levels)データ構造を持ったオブジェクトや配列の場合には、その変更内容を検出するために深いコピー(deep copy)を実行する必要があるかもしれません。
アプリケーションでそのような必要性が生じた場合は、Polymer.MutableData
ミックスインを使用することで、要素ごとにオブジェクトや配列のダーティチェックを回避できます。このミックスインは、利便性向上ためにいくらかパフォーマンスを犠牲にすることがあるかもしれません。詳細については、MutableDataミックスインの利用を参照してください。
関連タスク:
- [オブジェクトのサブプロパティの設定](https://github.com/jtakiguchi/polymer-docs-japanese-translation
/blob/master/docs/polymer2/data-system/work-with-object-and-array-data.md#set-a-property-or-subproperty-by-path) - [配列の変更](https://github.com/jtakiguchi/polymer-docs-japanese-translation
/blob/master/docs/polymer2/data-system/work-with-object-and-array-data.md#mutate-an-array) - [サブプロパティの変更をPolymerに通知](https://github.com/jtakiguchi/polymer-docs-japanese-translation
/blob/master/docs/polymer2/data-system/work-with-object-and-array-data.md#notify-polymer-of-subproperty-changes) - [配列の変更をPolymerに通知](https://github.com/jtakiguchi/polymer-docs-japanese-translation
/blob/master/docs/polymer2/data-system/work-with-object-and-array-data.md#notify-polymer-of-array-mutations)
バッチ処理によるプロパティの変更
バインディングシステム内におけるデータの伝播はバッチで処理されます、そうすることで複雑なオブザーバーや算出関数による変更は一度にまとまて実行されます。まとまった変更を生成する方法はいくつか存在します。:
-
要素が自身のプロパティを初期化する際は、まとまった変更を自動的に生成します。
-
オブジェクトまたは配列を設定する際は、まとまった変更を自動的に生成します。
-
setProperties
メソッドを使用することで、複数のプロパティをアトミック(atomically)に設定できます。
this.setProperties({item: 'Orange', count: 12 });
単一プロパティのアクセサメソッドについては、データは同期的に伝播されます。例えば、a
とb
の2つのプロパティを監視するオブザーバーがあったとします。以下の二つの動作の違いに留意して下さい。:
// observer fires twice
this.a = 10;
this.b = 20;
// observer fires once
this.setProperties({a: 10, b: 20});
データパス
データシステムにおいて、パスはスコープ内のプロパティまたはサブプロパティを識別するための文字列です。多くの場合、スコープはホスト要素です。例えば、次のような関係を考えてみましょう。:
-
<user-profile>
要素は、Javascriptのオブジェクトを参照するprimaryAddress
プロパティを持っています。 -
<address-card>
要素は、同じオブジェクトを参照するaddress
プロパティを持っています
重要なことですが、Polymerは、これらのプロパティが同じオブジェクトを参照していることを自動的に認識するわけではありません。<address-card>
がオブジェクトへの変更を行っても、<user-profile>
上でプロパティエフェクトは生じるわけではありません。
ある要素から別の要素へデータの変更が流れるようにするには、要素はデータバインディングによってコネクトされている必要があります。
データバインディングによるパスのリンク
データバインディングは、異なる要素のパス間にリンクを作成することができます。実は、**データバインディングは、異なる要素のパスにリンクを設定する唯一の手段です。**例えば、前のセクションの<user-profile>
の例で考えてみましょう。<address-card>
が<user-profile>
要素のローカルDOM内に存在する場合、二つのパスはデータバインディングを使ってコネクトすることができます。
<dom-module id="user-profile">
<template>
…
<address-card
address="{{primaryAddress}}"></address-card>
</template>
…
</dom-module>
これでパスはデータバインディングによってコネクトされました。
-
<user-profile>
要素は、Javascriptのオブジェクトを参照するprimaryAddress
プロパティを持っています。 -
<address-card>
要素は、同じオブジェクトを参照するaddress
プロパティを持っています。 - データバインディングは
<user-profile>
のパスprimaryAddress
と<address-card>
のパスaddress
をリンクさせます。
もし<address-card>
がオジェクトに対して監視可能な変更を加えた場合、<user-profile>
上でも同様にプロパティエフェクトが発生します。
データバインディングのスコープ
パスは、現在のデータバインディングのスコープに関連づいています。
どの要素にとっても最上位のスコープは要素のプロパティです。いつくかのデータバインディングのヘルパー要素([テンプレートリピーター](https://github.com/jtakiguchi/polymer-docs-japanese-translation
/blob/master/docs/polymer2/data-system/helper-elements.md#template-repeater)のような)では、新たに入れ子のスコープを導入します。
オブザーバーと算出プロパティの場合、スコープは常に要素のプロパティになります。
特別なパス
あるパスは、パスセグメント(多くの場合、各パスセグメントはプロパティの名前)の連続体です。
いくつか特殊なタイプのパスセグメントがあります。
- ワイルドカード記号(*)は、パス内の最後のセグメントとして使用できます。(例:
foo.*
)ワイルドカードパスは、配列の変更を含む、指定されたパス及びそのサブプロパティに対する全ての変更を表します。 - 文字列
splices
は、パス内の最後のセグメントとして使用できます。(例:foo.splices
)splicesは指定された配列における全ての変更を表します。 - 配列アイテムのパスは、(例:
foo.11
)ある配列内のアイテムを表し、数字のパスセグメントは配列のインデックスを表します。
ワイルドカードパス
ワイルドカード記号(*)をパス内の最後のセグメントとして使用すると、前のプロパティやそのサブプロパティへの変更を表します。例えば、users
という配列があった場合、users.*
と記述するとusers
配列自身またはそのサブプロパティへの変更について関心があることを示します。
ワイルドカードパスは、オブザーバー、算出プロパティ、算出バインディングだけに使用されます。データバインディングにおいてワイルドカードパスを使用することはできません。
配列変更パス
パスの最後のセグメントとしてsplices
を使用した場合には、指定された配列に対するすべての変更(追加や削除)を表します。例えば、users
という配列があった場合、パスusers.splices
は配列に行われたどんな追加や削除も認識します。
パス.splices
は、オブザーバー、算出プロパティ、算出バインディングで利用され、配列の変更に関心があることを示します。パス.splices
の監視は、ワイルドカードパスによって登録された変更のサブセットを提供します。(例えば、配列内のオブジェクトのサブプロパティを監視することはないかもしれません)。多くの場合、配列に対しては、ワイルドカードを使ったオブザーバーが役立ちます。
同一オブジェクトを参照する二つのパス
時々、一つの要素に同じオブジェクトを指し示す、二つのパスが存在することがあります。
例えば、要素に配列users
やオブジェクトselectedUser
を参照する二つのプロパティがあったとします。あるユーザーが選択されるとselectedUser
は配列内の一つのオブジェクトを参照します。
- プロパティ
users
は配列そのものを参照します。 - プロパティ
selectedUser
は配列内のアイテムを参照します。
この例では、要素には、配列内の二番目のアイテムを参照する以下の二つのパスが存在します。
selectedUser
-
users.1
(1
は配列users
内のアイテムのインデックスです)
デフォルトでは、Polymerには(users.1
のような)配列のパスをselectedUser
に関連付ける方法がありません。
このようなケースに最適な手段として、Polymerは、データバインディングのヘルパー要素<array-selector>
を用意しており、配列とその配列から選択されたアイテムの間でパスの結合を保持します。(<array-selector>
は、配列から複数のアイテムを選択する場合にも動作します)。
他のユースケースのために、二つのパスを関連付けるための命令的なメソッドとしてlinkPathsがあります。二つのパスがリンクされている場合、一方のパスに監視可能な変更を加えると、もう一方のパスでも同様にその変更を監視できます。
関連タスク:
- [二つのパスを同一オブジェクトにリンク](https://github.com/jtakiguchi/polymer-docs-japanese-translation
/blob/master/docs/polymer2/data-system/work-with-object-and-array-data.md#link-two-paths-to-the-same-object) - [配列の選択をデータバインド](https://github.com/jtakiguchi/polymer-docs-japanese-translation
/blob/master/docs/polymer2/data-system/helper-elements.md#data-bind-an-array-selection)
データフロー
PolymerはMediatorパターンを実装しており、ホスト要素は、要素自身とそのローカルDOMのノード間のデータフローを管理します。
二つの要素がデータバインディングでコネクトされると、データの変更は、ホストからターゲットへ下に向けて(downward)、またターゲットからホストへ上に向けて、あるいはその両方に向けて流すことができます。
ローカルDOM内の二つの要素が同一のプロパティにバインドされている場合、データはある要素から別の要素に流れるように思われますが、この流れはホストによって仲介(mediate)されたものです。一つの要素で生じた変更がホストへ上に伝播すると、ホストはその変更を他方の要素へ下に伝播させます。
データフローは同期的
データフローは同期的です。あなたの記述したコードが監視可能な変更を発生させると、その変更による全てのデータフローとプロパティエフェクトは、要素がアクションを明示的に遅延させない限り(例えば、非同期メソッドを呼び出すことによって)、次の行のJavaScriptが実行される前に発生します。
データフローの制御の仕組み
個々のバインディングによってサポートされるデータフローのタイプは、以下の項目に依存して決まります。:
- 使用されるバインディングアノテーションのタイプ。
- ターゲットプロパティーの設定。
二種類あるデータバインディングアノテーションは以下の通りです。:
-
自動的(automatic):上向き(ターゲットからホストへ)及び下向き(ホストからターゲットへ)のデータフローを許可します。自動バインディングには二重中括弧(
{{ }}
)を使用します。
<my-input value="{{name}}"></my-input>
-
一方向(one-way):下向きのデータフローだけ許可します。上向きのデータフローは無効です。一方向バインディングには二重角括弧(
[[ ]]
)を使用します。
<name-tag name="[[name]]"></name-tag>
次の設定フラグは、ターゲットプロパティ間におけるデータフローに影響を与えます。:
-
notify
:上向きのデータフローをサポートします。デフォルトでは、プロパティはfalse
(non-notifying)であり、上向きのデータフローはサポートされません。 -
readOnly
:下向きのデータフローを防ぎます。デフォルトでは、プロパティはfalse
(read/write)であり、下向きのデータフローをサポートします。
プロパティ定義の例
properties: {
// default prop, read/write, non-notifying.
basicProp: {
},
// read/write, notifying
notifyingProp: {
notify: true
},
// read-only, notifying
fancyProp: {
readOnly: true,
notify: true
}
}
以下の表は、ターゲットプロパティの設定を基に自動バインディングがどのようなタイプのデータフローをサポートするのか示したものです。
設定 | 結果 |
---|---|
notify: false readOnly: false |
一方向、下向き |
notify: false readOnly: true |
データフローなし |
notify: true readOnly: false |
双方向 |
notify: true readOnly: true |
一方向、上向き |
これとは対照的に、一方向バインディングは下向きのデータフローだけを許可するため、notify
フラグは結果に影響することはありません。:
設定 | 結果 |
---|---|
readOnly: false | 一方向、下向き |
readOnly: true | データフローなし |
**プロパティの設定(readOnlyやnotify)は、プロパティそのものに影響するだけで、サブプロパティには及びません。**特に、値にオブジェクトや配列を持つプロパティをバインディングした場合、ホストとターゲット要素の間で共有データが生成されますが、プロパティの設定による制御ができないので、どちらの要素でも共有されたオブジェクトや配列の変更を防ぐ方法はありません。詳細については、オブジェクト及び配列のデータフローを参照してください。
データフローの例
以下の例で、上記のさまざまなデータフローのシナリオを示しています。
例1:双方向バインディング
<script>
class XTarget extends Polymer.Element {
static get is() {return 'x-target';}
static get properties() {
return {
someProp: {
type: String,
notify: true
}
}
}
}
customElements.define(XTarget.is, XTarget);
</script>
...
<dom-module id="x-host">
<template>
<!-- changes to "value" propagate downward to "someProp" on target -->
<!-- changes to "someProp" propagate upward to "value" on host -->
<x-target some-prop="{{value}}"></custom-element>
</template>
<script>
class XHost extends Polymer.Element {
static get is() {return 'x-host';}
}
customElements.define(XHost.is, XHost);
</script>
例2:一方向バインディング(下向き)
バインディングを一方向のバインディング([[ ]]
)に変更すると、一方向バインディングが生成されます。この例では、例1と同じx-target
要素を使用しています。
<dom-module id="x-host">
<template>
<!-- changes to "value" propagate downward to "someProp" on target -->
<!-- changes to "someProp" don't propagate upward because of the one-way binding -->
<x-target some-prop="[[value]]"></custom-element>
</template>
<script>
class XHost extends Polymer.Element {
static get is() {return 'x-host';}
}
customElements.define(XHost.is, XHost);
</script>
例3:一方向バインディング(下向き)
同様に、双方向バインディングデリミタは使用しながら、someProp
プロパティでnotify: true
を省略すると、下向きの一方向バインディングが生成されます。
<script>
class XTarget extends Polymer.Element {
static get is() {return 'x-target';}
static get properties() {
return {
someProp: {
type: String // no notify: true
}
}
}
}
customElements.define(XTarget.is, XTarget);
</script>
...
<dom-module id="x-host">
<template>
<!-- changes to "value" propagate downward to "someProp" on target -->
<!-- changes to "someProp" are not notified to host due to notify:falsey -->
<x-target some-prop="{{value}}"></custom-element>
</template>
<script>
class XHost extends Polymer.Element {
static get is() {return 'x-host';}
}
customElements.define(XHost.is, XHost);
</script>
例4:一方向バインディング(上向き、子からホストへ)
<script>
class XTarget extends Polymer.Element {
static get is() {return 'x-target';}
static get properties() {
return {
someProp: {
type: String,
notify: true,
readOnly: true
}
}
}
}
customElements.define(XTarget.is, XTarget);
</script>
...
<dom-module id="x-host">
<template>
<!-- changes to "value" are ignored by child because "someProp" is read-only -->
<!-- changes to "someProp" propagate upward to "value" on host -->
<x-target some-prop="{{value}}"></custom-element>
</template>
<script>
class XHost extends Polymer.Element {
static get is() {return 'x-host';}
}
customElements.define(XHost.is, XHost);
</script>
例5:データフローなし/意味のない(nonsensial)状態
<script>
class XTarget extends Polymer.Element {
static get is() {return 'x-target';}
static get properties() {
return {
someProp: {
type: String,
notify: true,
readOnly: true
}
}
}
}
customElements.define(XTarget.is, XTarget);
</script>
...
<dom-module id="x-host">
<template>
<!-- changes to "value" are ignored by child because "someProp" is read-only -->
<!-- changes to "someProp" don't propagate upward because of the one-way binding -->
<x-target some-prop="[[value]]"></custom-element>
</template>
<script>
class XHost extends Polymer.Element {
static get is() {return 'x-host';}
}
customElements.define(XHost.is, XHost);
</script>
上向きおよび下向きデータフロー
ホスト要素はデータフローを管理しているので、ターゲット要素と直接的にやりとりすることができます。ホストは、ターゲット要素のプロパティを設定したり、メソッドを呼び出すことで下に向けてデータを伝播します。
- ホスト要素でプロパティが変更されると、ターゲット要素の対応するプロパティが設定され、関連するプロパティエフェクトが発生します。
Polymer要素は、イベントを使用してデータを上に向けて伝播させます。ターゲット要素は、監視可能な変更が発生するとノンバブリングイベントを発火します。(変更イベントに関する詳細は、[変更通知イベント](#change notification-events)で説明しています)。
双方向バインディングの場合、ホスト要素は、これらの変更イベントを監視(listen)し、その変更を伝播させます。これら変更は、プロパティを設定したり、関連するプロパティエフェクトが作用することで生じます。プロパティエフェクトには以下のようなものが含まれるかもしれません。
- 兄弟要素(sibling elements)へ変更を伝播させるためにデータバインディングをアップデートする。
- 変更を上に向けて伝播させるため、別の変更イベントを生成する。
- ターゲット要素におけるプロパティの変更によって、プロパティ変更イベントが発生します。
- ホスト要素はイベントを受け取ると対応するプロパティを設定し、関連するプロパティエフェクトを発生させます。
一方向バインディングアノテーションを使うと、ホストは変更リスナーを生成しませんので、データ変更が上に向かって伝播することはありません。
オブジェクトと配列のデータフロー
プロパティがオブジェクトや配列の場合、データフローは少々複雑になります。オブジェクトや配列は複数の要素から参照することができ、また、ある要素が共有された配列を変更したり、オブジェクトのサブプロパティを変更したりするのを防ぐ術はありません。
そのため、Polymerは配列やオブジェクトの内容を、常に双方向バインディング可能なものとして扱います。言い換えると:
- ターゲットプロパティが読み取り専用(
readOnly:true
)に設定されていても、データの更新はいつも下に向かって流れます。 - ターゲットプロパティが通知可能(
notify:ture
)に設定されていなくても、上向きのデータフローの変更イベントは常に発生します。
一方向バインディングアノテーションはイベントリスナーを生成しないため、これらの変更通知がホスト要素に伝播されないようにします。
変更通知イベント
要素は、以下のいずれかの監視可能な変更が発生した時点で変更通知イベントを発生させます。
- 通知設定されたプロパティへの変更
- サブプロパティの変更
- 配列の変更
イベントのtype
プロパティは、どのプロパティが変更されたか示しています。:property-changed
という命名規則従い、property
の部分はダッシュケース(dash-case)に変換したプロパティ名になります。(つまり、this.firstName
が変更されるとfirst-name-changed
が発火します)。
property-changed
リスナーを要素に手動で設定して、外部の要素、フレームワーク、またはライブラリにプロパティの変更を通知することができます。
イベントの内容は、変更によって異なります。
- プロパティの変更の場合、
detail.value
フィールドにプロパティの新しい値が含まれます。 - サブプロパティの変更の場合、サブプロパティへのパスが
detail.path
フィールドに含まれます、そして新しい値がdetail.value
フィールドに含まれます。 - 配列の変更の場合、
detail.path
フィールドはmyArray.splices
のように配列の変更パスになります。そして新しい値がdetail.value
フィールドに含まれます。
注意:**変更通知イベントの伝播は停止しないでください。**イベントオブジェクトの生成と廃棄を避けるため、Polymerは変更通知にキャッシュされたイベントオブジェクトを使用します。変更通知イベントで
stopPropagation
を呼び出ことで、**そのプロパティにおけるすべてのイベントを将来に渡って防止します。変更通知イベントはバブリングしないので、伝播を停止する必要はありません。
カスタム変更通知イベント
<input>
のようなネイティブ要素は、Polymerが上向きのデータフローに利用する変更通知イベントを用意していません。ネイティブのinput
要素で双方向データバインディングをサポートするために、Polymerではカスタム変更通知イベントをデータバインディングと関連付けることができるようになっています。例えば、テキスト入力にバインドすると、そのinput
またはchange
イベントを指定することができるようになります。:
<input value="{{firstName::change}}">
この例では、firstName
プロパティがinput
要素のvalue
プロパティにバインドされています。input
要素がchange
イベントを発生させるたびに、PolymerはfirstName
プロパティがinput
のvalue
と一致させるように更新し、また関連するプロパティエフェクトがあればすべて発生させます。イベントの内容は重要ではありません。
この手法は、特にネイティブのinput
要素にとって便利ですが、Polymerを使って作成されていないコンポーネント(プロパティを公開しており、その変更時にイベントを発生させる)に双方向バインディングを提供する手段としても使うことができます。
関連タスク:
- [Polymer要素でない要素への双方向バインディング](https://github.com/jtakiguchi/polymer-docs-japanese-translation
/blob/master/docs/polymer2/data-system/data-binding.md#two-way-bind-to-a-non-polymer-element)
要素の初期化
要素がローカルDOMを初期化する際は、要素はローカルDOMの子のプロパティを設定し、データバインディングの初期化も行います。
初期化処理の中では、ホストの値が優先されます。例えば、ホストプロパティがターゲットプロパティにバインドされている場合、host要素とtarget要素の両方でデフォルト値を指定すると、親のデフォルト値が使用されます。
プロパティエフェクト(property effect)
プロパティエフェクトは特定のプロパティ(やパス)に対する監視可能な変更によってトリガーされるアクションです。プロパティエフェクトには以下が含まれます。:
- 算出プロパティの再計算
- データバインディングの更新
- プロパティ値をホスト要素の属性に反映
- オブザーバーの呼び出し
- 変更通知イベントの発火
これらのプロパティエフェクトの実行順序は、しっかり定義されています。:
- 算出プロパティ
- データバインディング
- 反映された(reflected)値
- オブザーバー
- 変更通知イベント
このような実行順序によって、変更が下方に伝播される前に算出プロパティが再計算され、その変更がオブザーバーの実行前にローカルDOMの子に伝播されることが保証されます。
データバインディング
データバインディングは、ホスト要素のデータとホストのローカルDOM内のtarget
ノードのプロパティや属性の間にコネクションを確立します。要素のローカルDOMのテンプレートにアノテーションを追加することでデータバインディングを生成します。
アノテーションとは、ターゲット要素でデータバインディング用デリミタ{{ }}
又は[[ ]]
を使用した属性値です。
双方向プロパティバインディング:
target-property="{{hostProperty}}"
一方向プロパティバインディング:
target-property="[[hostProperty]]"
属性バインディング:
target-attribute$="[[hostProperty]]"
また、要素の本体にデータバインディングアノテーションを使用することもできます。これは、要素のtextContent
プロパティに対してバインディングを行なったのと同じことです。
<div>{{hostProperty}}</div>
デリミタ内のテキストは、次のいずれかになります。
- プロパティまたはサブプロパティのパス(
users
、address.street
)。 - 算出バインディング(
_computeName(firstName, lastName, locale)
)。 - 上記のそれぞれに、否定演算子(
!
)を後置したもの
詳細については、[データバインディング](https://github.com/jtakiguchi/polymer-docs-japanese-translation
/blob/master/docs/polymer2/data-system/data-binding.md)を参照してください。
MutableDataミックスインの使用
Polymer 1.xでは、ダーティチェック(dirty check)メカニズムを使用して、データシステムが余計な作業をするのを防いでいました。Polymer 2.xでもデフォルトでこのメカニズムを維持していますが、要素においてオブジェクトや配列に対するダーティチェックをオプトアウト(利用者が機能の使用を制限)できるようになっています。
デフォルトのダーティチェックメカニズムによって、次のコードではプロパティエフェクトを生成させません。:
this.property.subproperty = 'new value!';
this.notifyPath('property');
オブジェクトと配列に対する厳密なダーティチェックは、オブジェクトの同一性に基づいています。property
は依然として同じオブジェクトを指しているので、ダーティチェックは失敗し、サブプロパティの変更は伝播しません。代わりに、Polymerのset
メソッドや配列変更メソッドを使用するか、変更された正確なパス指定してnotifyPath
を呼び出す必要があります。
this.set('property.subproperty', 'new value!');
// OR
this.property.subproperty = 'new value!';
this.notifyPath('property.subproperty');
一般に、ダーティチェックメカニズムはパフォーマンスの向上をもたらします。以下いずれかの要件が当てはまるアプリケーションでは特にうまくいきます。:
- 不変データを使用する。
- 小さな変更についても必ずPolymerのデータ変更メソッドを使用する。
しかし、不変のデータを使用せず、Polymerのデータ関連メソッドを使用できないアプリケーションに対して、Polymer 2.0はPolymer.MutableDataミックスインを用意しています。
class MyMutableElement extends Polymer.MutableData(Polymer.Element) { ... }
MutableData
ミックスインは要素のダーティチェックを省略するので、上記コードは意図した通りに動作するでしょう。
this.property.subproperty = 'new value!';
this.notifyPath('property');
可変(mutable)データモードでは、プロパティエフェクトを呼び出す前に、いくつかの変更をバッチで処理することもできます:
this.property.arrayProperty.push({ name: 'Alice' });
this.property.stringProperty = 'new value!';
this.property.counter++;
this.notifyPath('property');
プロパティエフェクトを呼び出すために、set
メソッドを使ったり、または単純にトップレベルのプロパティを設定することもできます。:
this.set('property', this.property);
// or
this.property = this.property;
特定のサブプロパティを変更するのにset
メソッドを利用するのが、多くのケースで最も効率的な手段となります。しかし、MutableData
を使用する要素ではこのAPIを使用する必要はなく、データバインディングや状態管理を行う代替ライブラリを利用することで互換性をさらに高めることができます。
トップレベルでプロパティを再設定すると、そのプロパティ、サブプロパティ、配列のアイテムなどに対するプロパティエフェクトが全て再実行されてしまうので注意が必要です。さらに、ワイルドカードパス(prop.*
のような)を指定したオブザーバーには、トップレベルでの変更だけが通知されます。:
// With MutableData mixin
// 'property.*' observers fire with the path 'property'
this.property.deep.path = 'another new value';
this.notifyPath('property');
set
メソッドを利用して特定のパスを設定すると、細かい通知も生成します。
// 'property.*' observers fire with the path 'property.deep.path'
this.set('property.deep.path', 'new value');
要素のプロパティが文字列、数値、ブール値などのプリミティブな値しか取らない場合には、MutableData
を使用する必要はありません。これらの値は常にダーティチェックされておりMutableData
にはメリットがありません。これは多くのシンプルなUI要素について言えることです。MutableData
は複雑で再利用可能な要素(dom-repeat
またはiron-list
のような)や、複雑な状態の情報を持つアプリケーション固有の要素に対しては役立つでしょう。
MutableData
ミックスインは、要素のShadow DOMの子には影響を与えないことに注意してください。Polymer.MutableData
ミックスインを使用しないすべての要素は、すべてデフォルトのダーティチェックポリシーに従います。
dom-repeat
要素を使用している場合は、mutableData
プロパティを設定することで可変データモードを有効にすることができます:
<!-- standard dom-repeat in MutableData mode -->
<template is="dom-repeat" items="{{items}}" mutable-data>
<div>{{item.name}}</div>
</template>
再利用可能な要素のオプションとしての可変(mutable)データ
構造化されたデータを取り込む再利用可能な要素を構築する場合は、Polymer.OptionalMutableDataミックスインが使用できます。このミックスインを使用すると、要素上にmutableData
プロパティを設定することで、MutableData
モードを選択できます。
class MyStructuredDataElement extends Polymer.OptionalMutableData(Polymer.Element) {
static get is() { return 'my-structured-data-element' }
}
これにより、要素の利用者は、標準のデータフローまたは可変データモードのいずれかで要素を利用することができます。
<!-- custom element using standard data flow -->
<my-structured-data-element data="{{someData}}">
</my-structured-data-element>
<!-- custom element using MutableData mode -->
<my-structured-data-element data="{{someData" mutable-data>
</my-structured-data element>
dom-repeat
要素は、このミックスインを利用して構成された要素の例です。