聚合物数据绑定和 Immutable.js 在一起
Polymer data-binding and Immutable.js together
考虑自定义聚合物元素的以下用法
<app-header bar-foo="[[abc.def.ghi]]" app-title="[[appTitle]]"></app-header>
这里我将两个变量绑定到自定义元素app-header
。现在,当我想更新值时,您可能希望按如下方式执行此操作(在 app-header
! 的父级中):
this.abc.def.ghi = 10;
this.appTitle = 'New title';
但是,这只会更新 appTitle
而不是 abc.def.ghi
。为了也更新此值,您需要按以下方式执行此操作:
this.abc = {def: {ghi: 10}};
如果有人知道这是为什么,请告诉我!!
反正就是因为这个,我想用Immutable
!
然而,这引入了一些关于数据如何绑定到自定义元素的问题
这是一个示例片段:
<dom-module id="my-app">
<template>
<app-header hits="[[state.get('page').get('hits')]]"></app-header>
</template>
<script>
(function () {
class MyApp {
beforeRegister() {
this.is = 'my-app';
this.properties = {
state: {
type: Object,
notify: true,
value: Immutable.Map({
page: Immutable.Map({hits: 10})
})
}
};
}
}
Polymer(MyApp);
})();
</script>
</dom-module>
所以在绑定数据到元素时出现问题:
<app-header hits="[[state.get('page').get('hits')]]"></app-header>
这样的事情有可能吗?还是我做的其他事情完全错了?
当你更新结构数据时,你应该使用聚合物API。这将触发更改的事件,绑定的数据将更新。查看路径更改通知中的 this article。在这种情况下,您需要将代码更改为:
this.set('abc.def.ghi', 10);
我对Immutable不熟悉,但是Polymer不支持这种表达方式。
hits="[[state.get('page').get('hits')]]"
您可以绑定到元素或计算函数的(子)属性。计算函数必须在您的元素中定义。您不能在数据绑定中对任意对象调用任意函数。
也许,使用 set
API 将消除使用 Immutable 的需要。
我知道这个 post 有点老了,但我想回答是因为我想探索一起使用 Immutable JS 和聚合物,因为:
- 我的团队使用聚合物来构建应用程序,无法更改
- 不可变使得撤消和重做的实现变得微不足道,这是一项要求
我找到了一种在 polymer 中绑定不可变 js 对象的方法,但请注意,它既丑陋又不可靠。
绑定到不可变对象
为了让聚合物访问不可变的 js 属性,您需要在元素原型上创建一个类型为 Object
的 属性,作为不可变之间的一种 'adapter' js 和聚合物。
此对象 属性 必须使用 javascript getters(或 Object.defineProperty
),以便聚合物 API 可以以 [=14= 的形式访问对象] 但实际实现将访问不可变对象。
let immutableMap = Immutable.map({count: 0, text: 'world'});
Polymer({
// ...
properties: {
/**
* This property is an 'adapter' to assign a property path to access the immutablejs object.
* The property starts with an underscore because we don't want the user to set it
*/
_adapter: {
type: Object,
value: {
/**
* the properties of this object use
* [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)
* so that polyer can access the object like `obj.count`
*/
get count() { return immutableMap.get('count'); },
get text() { return immutableMap.get('text'); }
}
}
}
// ...
});
所以在上面的例子中,我创建了一个 Object
类型的 属性,它有两个方法:count
和 text
。因为这些方法以 get
为前缀,所以它们应该只能通过方法名称访问——它是一个 getter.
这样做可以使聚合物绑定到 _adapter
对象,然后该对象 'gets' 来自 immutableMap
的值。
更改不可变对象
实现此功能所需的第二部分是更改不可变对象。由于对象是不可变的,双向绑定不是一种选择。使用不可变时,您必须添加事件侦听器,以重新分配不可变对象并重新渲染。
要做到这一点,您必须 override dirty checking in polymer. I've tried use notifyPath
but I found that that doesn't work as excepted. notifyPath
工作。
警告
由于我们覆盖了脏检查并且我们没有使用虚拟-dom 库(如 React),使用不可变对象需要重新渲染任何使用 _adapter
的元素每次在不可变对象中发生更改时。这可以通过使用 notifyPath
来改善。
完成演示(撤消)
这里是immutable js和polymer一起的完整demo。我添加了一个撤消功能,作为使用不可变的一点激励——不可变的 JS 使撤消和重做非常简单:D
<base href="https://cdn.rawgit.com/download/polymer-cdn/1.7.0/lib/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="polymer/polymer.html">
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>
<!-- Defines element markup -->
<dom-module id="hello-world">
<template>
<div>
<!--use only one-way binding because the object is immutable-->
<button on-tap="onButtonClick">clicks <span>[[_adapter.count]]</span></button>
</div>
<div>
<input on-input="onInput" type="text" value="[[_adapter.text]]">
<h1>hello, <span>[[_adapter.text]]</span>!</h1>
</div>
<div>
<button on-tap="onUndo">Undo</button>
</div>
</template>
<!-- Registers custom element -->
<script>
// immutable map is defined out of the scope of the polymer object
let immutableMap = Immutable.Map({
count: 0,
text: 'world'
});
// add undos
let undos = [];
Polymer({
is: 'hello-world',
/**
* every event should reassign the `immutableMap` then manually `notifyPath`
*/
onButtonClick: function () {
undos.push(immutableMap);
immutableMap = immutableMap.update('count', count => count + 1);
this.notifyPath('_adapter.count');
},
onInput: function (event) {
undos.push(immutableMap);
immutableMap = immutableMap.set('text', event.target.value);
this.notifyPath('_adapter.text');
},
onUndo: function () {
if (undos.length) {
immutableMap = undos.pop();
}
const obj = this._adapter;
this._adapter = {};
this._adapter = obj;
},
properties: {
/**
* This property is an 'adapter' to assign a property path to access the immutablejs object.
* The property starts with an underscore because we don't want the user to set it
*/
_adapter: {
type: Object,
value: {
/**
* the properties of this object use
* [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)
* so that polymer can access the object like `obj.count`
*/
get count() { return immutableMap.get('count'); },
get text() { return immutableMap.get('text'); }
}
}
}
});
</script>
</dom-module>
<hello-world></hello-world>
考虑自定义聚合物元素的以下用法
<app-header bar-foo="[[abc.def.ghi]]" app-title="[[appTitle]]"></app-header>
这里我将两个变量绑定到自定义元素app-header
。现在,当我想更新值时,您可能希望按如下方式执行此操作(在 app-header
! 的父级中):
this.abc.def.ghi = 10;
this.appTitle = 'New title';
但是,这只会更新 appTitle
而不是 abc.def.ghi
。为了也更新此值,您需要按以下方式执行此操作:
this.abc = {def: {ghi: 10}};
如果有人知道这是为什么,请告诉我!!
反正就是因为这个,我想用Immutable
!
然而,这引入了一些关于数据如何绑定到自定义元素的问题
这是一个示例片段:
<dom-module id="my-app">
<template>
<app-header hits="[[state.get('page').get('hits')]]"></app-header>
</template>
<script>
(function () {
class MyApp {
beforeRegister() {
this.is = 'my-app';
this.properties = {
state: {
type: Object,
notify: true,
value: Immutable.Map({
page: Immutable.Map({hits: 10})
})
}
};
}
}
Polymer(MyApp);
})();
</script>
</dom-module>
所以在绑定数据到元素时出现问题:
<app-header hits="[[state.get('page').get('hits')]]"></app-header>
这样的事情有可能吗?还是我做的其他事情完全错了?
当你更新结构数据时,你应该使用聚合物API。这将触发更改的事件,绑定的数据将更新。查看路径更改通知中的 this article。在这种情况下,您需要将代码更改为:
this.set('abc.def.ghi', 10);
我对Immutable不熟悉,但是Polymer不支持这种表达方式。
hits="[[state.get('page').get('hits')]]"
您可以绑定到元素或计算函数的(子)属性。计算函数必须在您的元素中定义。您不能在数据绑定中对任意对象调用任意函数。
也许,使用 set
API 将消除使用 Immutable 的需要。
我知道这个 post 有点老了,但我想回答是因为我想探索一起使用 Immutable JS 和聚合物,因为:
- 我的团队使用聚合物来构建应用程序,无法更改
- 不可变使得撤消和重做的实现变得微不足道,这是一项要求
我找到了一种在 polymer 中绑定不可变 js 对象的方法,但请注意,它既丑陋又不可靠。
绑定到不可变对象
为了让聚合物访问不可变的 js 属性,您需要在元素原型上创建一个类型为 Object
的 属性,作为不可变之间的一种 'adapter' js 和聚合物。
此对象 属性 必须使用 javascript getters(或 Object.defineProperty
),以便聚合物 API 可以以 [=14= 的形式访问对象] 但实际实现将访问不可变对象。
let immutableMap = Immutable.map({count: 0, text: 'world'});
Polymer({
// ...
properties: {
/**
* This property is an 'adapter' to assign a property path to access the immutablejs object.
* The property starts with an underscore because we don't want the user to set it
*/
_adapter: {
type: Object,
value: {
/**
* the properties of this object use
* [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)
* so that polyer can access the object like `obj.count`
*/
get count() { return immutableMap.get('count'); },
get text() { return immutableMap.get('text'); }
}
}
}
// ...
});
所以在上面的例子中,我创建了一个 Object
类型的 属性,它有两个方法:count
和 text
。因为这些方法以 get
为前缀,所以它们应该只能通过方法名称访问——它是一个 getter.
这样做可以使聚合物绑定到 _adapter
对象,然后该对象 'gets' 来自 immutableMap
的值。
更改不可变对象
实现此功能所需的第二部分是更改不可变对象。由于对象是不可变的,双向绑定不是一种选择。使用不可变时,您必须添加事件侦听器,以重新分配不可变对象并重新渲染。
要做到这一点,您必须 override dirty checking in polymer. I've tried use notifyPath
but I found that that doesn't work as excepted.notifyPath
工作。
警告
由于我们覆盖了脏检查并且我们没有使用虚拟-dom 库(如 React),使用不可变对象需要重新渲染任何使用 _adapter
的元素每次在不可变对象中发生更改时。这可以通过使用 notifyPath
来改善。
完成演示(撤消)
这里是immutable js和polymer一起的完整demo。我添加了一个撤消功能,作为使用不可变的一点激励——不可变的 JS 使撤消和重做非常简单:D
<base href="https://cdn.rawgit.com/download/polymer-cdn/1.7.0/lib/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="polymer/polymer.html">
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>
<!-- Defines element markup -->
<dom-module id="hello-world">
<template>
<div>
<!--use only one-way binding because the object is immutable-->
<button on-tap="onButtonClick">clicks <span>[[_adapter.count]]</span></button>
</div>
<div>
<input on-input="onInput" type="text" value="[[_adapter.text]]">
<h1>hello, <span>[[_adapter.text]]</span>!</h1>
</div>
<div>
<button on-tap="onUndo">Undo</button>
</div>
</template>
<!-- Registers custom element -->
<script>
// immutable map is defined out of the scope of the polymer object
let immutableMap = Immutable.Map({
count: 0,
text: 'world'
});
// add undos
let undos = [];
Polymer({
is: 'hello-world',
/**
* every event should reassign the `immutableMap` then manually `notifyPath`
*/
onButtonClick: function () {
undos.push(immutableMap);
immutableMap = immutableMap.update('count', count => count + 1);
this.notifyPath('_adapter.count');
},
onInput: function (event) {
undos.push(immutableMap);
immutableMap = immutableMap.set('text', event.target.value);
this.notifyPath('_adapter.text');
},
onUndo: function () {
if (undos.length) {
immutableMap = undos.pop();
}
const obj = this._adapter;
this._adapter = {};
this._adapter = obj;
},
properties: {
/**
* This property is an 'adapter' to assign a property path to access the immutablejs object.
* The property starts with an underscore because we don't want the user to set it
*/
_adapter: {
type: Object,
value: {
/**
* the properties of this object use
* [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)
* so that polymer can access the object like `obj.count`
*/
get count() { return immutableMap.get('count'); },
get text() { return immutableMap.get('text'); }
}
}
}
});
</script>
</dom-module>
<hello-world></hello-world>