聚合物数据绑定和Immutable.js在一起

时间:2015-10-20 12:31:00

标签: javascript data-binding polymer immutable.js custom-element

考虑以下自定义聚合物元素的用法

<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>

这样的事情是否可能,或者我做其他事情完全错了?

2 个答案:

答案 0 :(得分:1)

更新结构数据时,应使用Polymer API。这将触发更改的事件,绑定的数据将更新。查看路径更改通知中的this article。在这种情况下,您需要将代码更改为:

this.set('abc.def.ghi', 10);

我不熟悉Immutable,但是,Polymer不支持这种表达式。

hits="[[state.get('page').get('hits')]]"

您可以绑定到元素的(子)属性或计算函数。必须在元素中定义计算功能。您无法在数据绑定中的任意对象上调用任意函数。 也许,使用set API将无需使用Immutable。

答案 1 :(得分:1)

我知道这篇文章有点陈旧,但我想回答,因为我想探索使用Immutable JS和聚合物,因为:

  1. 我的团队使用聚合物来构建应用程序,无法改变
  2. immutable使得实现undos和redos变得微不足道,这是一项要求
  3. 我找到了一种在聚合物中绑定不可变js对象的方法,但是要注意,它很丑陋和hacky。

    绑定到不可变对象

    为了让聚合物访问不可变的js属性,你需要在元素原型上创建一个类型为Object的属性,它作为不可变的js和聚合物之间的一种“适配器”。

    此对象属性必须使用javascript getters(或Object.defineProperty),以便聚合物API可以object.property的形式访问对象,但实际的实现将访问不可变对象

    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类型的属性,它有两种方法:counttext。因为这些方法前缀为get,所以它们只能通过方法名称进行访问 - 它是getter

    这样做可以使聚合物绑定到_adapter对象,然后该对象从immutableMap'获取'值。

    更改不可变对象

    实现此功能所需的第二部分是更改不可变对象。由于对象是不可变的,因此双向绑定不是一种选择。使用immutable时,必须添加将重新分配不可变对象并重新呈现的事件侦听器。

    为此,您必须override dirty checking in polymer我尝试过使用notifyPath,但我发现这不起作用。 notifyPath应该有用。

    买者

    由于我们正在重写脏检查并且我们没有使用虚拟dom库(如react),因此使用不可变对象需要在每次发生更改时重新呈现使用_adapter的任何元素。一个不可变的对象。这可以通过使用notifyPath来改善。

    完整演示(撤消)

    以下是不可变的js和聚合物的完整演示。我添加了一个撤销功能,以便使用不可变的一点点激励 - 不可变的JS使得undos和redos非常简单: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>