autorun 的反应式计算可以依赖于集合的一部分吗?

Can autorun's reactive computation be dependent on part of a collection?

我想在 Meteor 中创建一个具有 Tracker.autorun 的模板,当文档的一部分发生变化时,它专门 运行s --- 但当文档的其他部分发生变化时则不会。 所以这是使用 minimongo 集合和 template.autorun

的示例代码

parent.html

{{#each items}}
  {{> child}}
{{/each}}

child.html

<div>{{title}}</div>
<p>{{description}}</p>

Minimongo 合集

LocalProject.findOne() output:
    "items": [
      {
        "title": "hi",
        "description": "test"
      },
      {
        "title": "hi 2",
        "description": "test 2"
      },
    ],
    "otherstuff:{//etc}

child.js

Template.child.onRendered(function(){
    this.autorun(function() {
        var data = Template.currentData();
        doSomething(data.title,data.description)
    });
});

addnewitem.js

LocalProject.update(currentEditingProjectID,{ $push: { 'items': newItem }},function(error,result){
    if(error){console.log(error)}
});

问题是,每当我 运行 addnewitem.js 时,我所有的 Template.child auto运行 都会执行,即使它们的反应数据源(Template.currentData ()) 没有改变,除非它是我更新的特定项目。同样,如果我想更新现有项目,而不仅仅是向数组添加新项目,则会执行每个项目的所有 auto运行s。

那么有没有一种方法可以使用此模型为 auto运行 创建对文档的特定部分具有反应性粒度的依赖项?

有一个专门用于此的工具 - 3stack:embox-value 提供反应隔离和值缓存。

使用您的示例,您可以隔离对 title/description 的更改,如下所示:

首先,添加包

meteor add 3stack:embox-value
meteor add ejson

然后,更新您的代码:

Template.child.onRendered(function(){
     // creates a reactive data source, that only triggers "changes"
     // when the output of the function changes.
     var isolatedData = this.emboxValue(function(){
        var data = Template.currentData();
        return {title: data.title, description: data.description}
    }, {equals: EJSON.equals})

    this.autorun(function() {
         // This autorun will only execute when title or description changes.
        var data = isolatedData()
        doSomething(data.title,data.description)
    });
});

我不认为使用自动运行是可行的方法。我会为每个项目设置单独的反应依赖项,或者使用 observe/observeChange.

第一个想法

parent.html:

{{#each items}}
  {{> child}}
{{/each}}

parent.js:

Template.parent.helpers({
  // Returns only item ids
  items: function() { 
    return Items.find({}, { fields: { _id: 1 } });
  }
});

child.html:

{{#each items}}
  {{#with item=getItem}}
    <div>{{item.title}}</div>
    <p>{{item.description}}</p>
  {{/with}}
{{/each}}

child.js:

Template.child.helpers({
  getItem: function() {
    // Get the item and set up a reactive dependency on this particular item
    var item = Items.find(this._id);

    // After item has been displayed, do something with the dom
    Tracker.afterFlush(function () {
      doSomething(item.title, item.description);
    });

    return item;
  }
});

第二个想法

parent.html:

{{#each items}}
  {{> child}}
{{/each}}

parent.js:

function do(item) {
  Tracker.afterFlush(function () {
    doSomething(item.title, item.description);
  });
}

Template.parent.onCreated({
  this.items = Items.find({});
  this.handle = this.items.observe({
    added:   function(item)             { do(item); },
    changed: function(oldItem, newItem) { do(newItem); },
  });  
});

Template.parent.onDestroyed({
  this.handle.stop();
});

Template.parent.helpers({
  items: function() { 
    return Template.instance().items;
  }
});

child.html:

{{#each items}}
  <div>{{title}}</div>
  <p>{{description}}</p>
{{/each}}