【问题标题】:How to detect change in a property of an object in an array with Polymer?如何使用 Polymer 检测数组中对象属性的变化?
【发布时间】:2015-03-20 08:10:17
【问题描述】:

假设我想使用 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),则什么也不会发生。这就是我的问题的根源......

【问题讨论】:

    标签: google-chrome polymer object.observe


    【解决方案1】:

    只要我的 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>
    

    我很难理解为什么它在没有模板的情况下也能工作。如果我们很好地保持我们的监听器,我们会看到它不是调用但剩余的是更新......

    【讨论】:

      【解决方案2】:

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

      我已经用以下代码进行了测试:

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

      请戳这里:http://embed.plnkr.co/YgqtKgYRaRTZmoKEFwBy/preview

      【讨论】:

      • 谢谢霍拉西奥。我已将 changeState 方法的代码更改为:changeState: function(e) { var _id = e.target.getAttribute('data-task-id'); var tmp = { name: this.tasks[_id].name, done: !this.tasks[_id].done}; this.tasks[_id] = tmp; } 并且它可以工作...
      【解决方案3】:

      您没有更新changeState 方法中观察到的“任务”数组。改成这样:

      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
      }
      

      【讨论】:

      • 感谢您的建议,但这并不能解决我的问题。有关详细信息,请参阅我的编辑。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-19
      • 2021-08-25
      • 1970-01-01
      • 2010-09-21
      • 2017-08-18
      • 2017-08-15
      相关资源
      最近更新 更多