如何使用 Polymer 检测数组中对象的 属性 的变化?

How to detect change in a property of an object in an array with Polymer?

假设我想用 Polymer (v0.5.5) 创建一个待办事项列表。在我的元素中,我定义了一个 属性 tasks,这是一个包含对象列表的数组,例如 { name: 'foo', done: false }。我想显示剩余任务数,所以我需要检测数组中包含的对象的属性 done何时发生变化。

这是代码的摘录:

<polymer-element name="todo-list">
  <template>
    <span>You have {{ tasks.length }} tasks, {{ remaining }} remaining</span>
    ...
  </template>
  <script>
      Polymer({
          tasks: [
              {name: "foo", done: false},
              {name: "bar", done: true}
          ],
          get remaining() {
              return this.tasks.filter(function(t) { return !t.done }).length;
          }
          changeState: function(e) {
              var _task = this.tasks[e.target.getAttribute('data-task-id')];
              _task.done = !_task.done;
          }
      });
  </script>
</polymer-element>

对于 Firefox,它可以工作,但不能用于 Chrome (41.x)。事实上,Chrome 只检测数组本身的变化(例如,如果我添加一个新任务,remaining 计数会正确更新)。

我该怎么做?

谢谢


编辑,关于安迪的回答

当我做那种事时:

var tasks = tasks: [
          {name: "foo", done: false},
          {name: "bar", done: true},
          {name: "baz", done: false}
      ];
Object.observe(tasks, function() { alert('Modification'); }

如果我对数组本身进行修改(如 tasks.push({...})),则会显示一个弹出窗口。但是,如果我更改数组中包含的对象的 属性(例如 tasks[0].done = true),则什么也不会发生。这就是我问题的根源...

您没有在 changeState 方法中更新观察到的 "task" 数组。改成这样:

changeState: function(e) {
 this.tasks[e.target.getAttribute('data-task-id')].done = !this.tasks[e.target.getAttribute('data-task-id')].done;
}

基本上,您是通过重新分配数组的值来创建一个局部变量 - 在您显式设置它之前没有反向引用。现在上面的代码很长,所以你也可以这样做:

changeState: function(e) {
 var _task = this.tasks[e.target.getAttribute('data-task-id')];
 _task.done = !_task.done;

 // re assign
 this.tasks[e.target.getAttribute('data-task-id')] = _task;e
}

恐怕我不明白这个问题,罗曼。

我用下面的代码测试过:

<polymer-element name="my-component" attributes="status count">
  <template>
    <style>
    </style>
    <div >
      <h1>You have {{ tasks.length }} tasks, {{ remaining }} remaining</h1>

      <div 
        style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
        on-click="{{ doTask }}"
      >
        Click to mark as done
      </div>

      <div>
        {{ tasks[0].done }}
      </div>
    </div>
  </template>
  <script>
  Polymer("my-component", {
    tasks: [
              {name: "foo", done: false},
              {name: "bar", done: true}
          ],
    get remaining() {
              return this.tasks.filter(function(t) { return !t.done }).length;
          },
    doTask: function() {
      this.tasks[0].done = true;
    }
  });
</script>
</polymer-element>

当我点击按钮时,标签的值发生变化,即 remining getter 检测到变化。我已经在 Linux.

上的 Chromium 41 和 Firefox 中进行了测试

您可以在 http://embed.plnkr.co/HXaKsQHjchqwe0P3bjy5/preview

上测试我的代码

能否请您提供更多信息,说明您想做什么以及它与我的代码有何不同?

编辑

通过 Twitter 与@romaintaz 交谈后,问题似乎只发生在按钮位于 if 模板内时,如下所示:

<div >
  <h1>You have {{ tasks.length }} tasks, {{ remaining }} remaining</h1>

  <template repeat="{{ task, taskIndex in tasks }}">

    <template if="{{task.done}}">
      <button 
        style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
        on-click="{{ doTask }}"
      >Click 1</button>
    </template>

    <template if="{{!task.done}}">
      <button 
        style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
        on-click="{{ doTask }}"
      >Click 2</button>
    </template>

  </template>

  <div>
    {{ tasks[0].done }}
  </div>

在这种情况下,removing getter 不再检测列表的一项属性的更改。

目前我只有一个 quick'n'dirty 解决方案:与其只更改列表项中的一个 属性,不如更改整个列表项,然后 getter 看到它。

示例:

<polymer-element name="my-component" attributes="status count">
  <template>
    <style>
    </style>
    <div >
      <h1>You have {{ tasks.length }} tasks, {{ remaining }} remaining</h1>

      <template repeat="{{ task, taskIndex in tasks }}">

        <template if="{{task.done}}">
          <button 
            style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
            on-click="{{ doTask }}"
          >Click 1</button>
        </template>

        <template if="{{!task.done}}">
          <button 
            style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
            on-click="{{ doTask }}"
          >Click 2</button>
        </template>

      </template>

      <div>
        {{ tasks[0].done }}
      </div>
    </div>
  </template>
  <script>
  Polymer("my-component", {
    tasks: [
              {name: "foo", done: false},
              {name: "bar", done: true}
          ],
    get remaining() {
              return this.tasks.filter(function(t) { return !t.done }).length;
          },
    doTask: function() {
      tmp = this.tasks[0];
      tmp.done = true
      this.tasks[0] = {};
      this.tasks[0] = tmp;
    },
    observe: {
      tasks: 'validate'
    }, 
    validate: function(oldValue, newValue) {
    }
  });
</script>
</polymer-element>

Plunkr 在这里:http://embed.plnkr.co/YgqtKgYRaRTZmoKEFwBy/preview

只是我的 2 美分

事实上,问题与模板中是否存在按钮无关。 您只需在列表中使用重复模板即可重现问题。 下面的代码对此进行了演示。

  <h1>You have {{ tasks.length }} tasks, {{ remaining }} remaining</h1>

  <button
    style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
    on-click="{{ doTask }}"
    >
    Click to mark as done
  </button>

  <ul>
    <template repeat="{{ task in othertasks }}">
      <li>{{task}}</li>
    </template>
  </ul>

  <div>
    {{ tasks[0].done }}
  </div>
</template>
<script>
  Polymer({
    tasks: [
      {name: "foo", done: false},
      {name: "bar", done: true}
    ],
    othertasks: ["foo","bar"],        
    get remaining() {
      return this.tasks.filter(function(t) { return !t.done }).length;
    },
    doTask: function () {
      this.tasks[0].done = true;         
    }
  });
</script>
</polymer-element>

其实我觉得很自然。当我们看规范时Object.observe()。实际上在一个数组上,只有当我们:

  • 删除一个元素
  • 添加一个元素
  • 修改一个项目(这里我们说 参考变化)

因此,如果您更改数组中某个元素的内部属性,它将不会触发。 如果我们在 ready 方法中添加一个监听器,我们将看到它不是由我们的 doTask 方法触发的。这就是 Horacio 的 hack 起作用的原因。他更换了对象。 另一种解决方案是使用

手动通知更改
Object.getNotifier(this.tasks).notify

以下是完整版

<polymer-element name="todo-list">
<template>

  <h1>You have {{ tasks.length }} tasks, {{ remaining }} remaining</h1>

  <button
    style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
    on-click="{{ doTask }}"
    >
    Click to mark as done
  </button>

  <ul>
    <template repeat="{{ task in othertasks }}">
      <li>{{task}}</li>
    </template>
  </ul>

  <div>
    {{ tasks[0].done }}
  </div>
</template>
<script>
  Polymer({
    tasks: [
      {name: "foo", done: false},
      {name: "bar", done: true}
    ],
    othertasks: ["foo","bar"],
    ready: function () {
      Object.observe(this.tasks, function(change) {           
        console.log(change); // What change
      });
    },
    get remaining() {
      return this.tasks.filter(function(t) { return !t.done }).length;
    },
    doTask: function () {
      this.tasks[0].done = true;
      Object.getNotifier(this.tasks).notify({
        type: 'update',
        name: '0'
      });
    }
  });
</script>
</polymer-element>

我很难理解为什么没有模板也能正常工作。如果我们保持我们的听众很好,我们会发现它不是调用,而是更新...