首页 > 解决方案 > 误解 `each...in` 渲染与反应变量

问题描述

我是Meteor/Blaze的新手,但这是我公司正在使用的。

我很难理解 Blaze 如何决定渲染基于ReactiveDict

TL;博士

ReactiveDict我从父数组中的数组创建了一些子模板。更改时子模板中的数据不会刷新,ReactiveDict我不明白为什么。

我可能对 Blaze 渲染有误解。你能帮帮我吗?


家长

父 Html 模板

<template name="parent">
    {{#each child in getChildren}}
        {{> childTemplate (childArgs child)}}
    {{/each}}
</template>

父 Javascript

反应变量

该模板从getChildren仅检索ReactiveDict.

// Child data object
const child = () => ({
  id: uuid.v4(),
  value: ""
});

// Create a reactive dictionary
Template.parent.onCreated(function() {
  this.state = new ReactiveDict();
  this.state.setDefault({ children: [child()] });
});
// Return the children from the reactive dictionary
Template.parent.helpers({
  getChildren() {
    return Template.instance().state.get('children');
  }
});
子模板参数(来自模板)

父模板为子模板提供了一些用于设置默认值和回调的数据。每个都使用一个函数实例化,该childArgs函数使用子id设置正确的数据和回调。单击add按钮时,它会将一个子项添加childrenReactiveDict. 单击delete按钮时,它会childrenReactiveDict.

Template.parent.helpers({
    // Set the children arguments: default values and callbacks
    childArgs(child) {
        const instance = Template.instance();
        const state = instance.state;
        const children = state.get('children');
        return {
           id: child.id,
           // Default values
           value: child.value, 
           // Just adding a child to the reactive variable using callback
           onAddClick() {
                const newChildren = [...children, child()];
                state.set('children', newChildren); 
           },
           // Just deleting the child in the reactive variable in the callback
           onDeleteClick(childId) { 
                const childIndex = children.findIndex(child => child.id === childId);
                children.splice(childIndex, 1);
                state.set('children', children); 
            }
        }
    }
})

孩子

子 html 模板

该模板显示来自父级和 2 个按钮的数据,add以及delete

<template name="child">
        <div>{{value}}</div>
        <button class="add_row" type="button">add</button>
        <button class="delete_row" type="button">delete</button>
</template>

子 javascript(事件)

这里调用的两个函数是作为参数从模板传递的回调。

// The two functions are callbacks passed as parameters to the child template
Template.child.events({
  'click .add_row'(event, templateInstance) {
    templateInstance.data.onAddClick();
  },
  'click .delete_row'(event, templateInstance) {
    templateInstance.data.onDeleteClick(templateInstance.data.id);
  },

问题

我的问题是,当我删除一个孩子(使用回调设置ReactiveDictlikeonAddClick()函数)时,我的数据没有正确呈现。

文本示例:

我添加这样的行。

child 1 | value 1
child 2 | value 2
child 3 | value 3

当我删除child 2,我得到这个:

child 1 | value 1
child 3 | value 2

我想要这个:

child 1 | value 1
child 3 | value 3

我正在使用函数中的child数据初始化。childArgsTemplate.child.onRendered()

  1. :删除ingetChildren()时调用该函数,并且我在变量 ( in ) 中有正确的数据。childReactiveDictchildrenReactiveDict
  2. :如果我有 3 个孩子并且我删除了一个,则模板只呈现 2 个孩子。
  3. :然而孩子的onRendered()从来没有被调用过(孩子的onCreated()功能也没有)。这意味着为模板显示的数据是错误的。

图片示例

我正在添加图片以帮助理解:

正确的html

显示的 HTML 是正确的:我有 3 个孩子,我删除了第二个。在我的 HTML 中,我可以看到显示的两个孩子在其 div 中具有正确的 ID。然而显示的数据是错误的。

正确的html

过时的数据

我已经删除了第一张照片中的第二个孩子。显示的孩子应该是第一个和第三个。在控制台日志中,我的数据是正确的。红色数据是第一位的。紫色是第三个。

然而我们可以看到被删除的孩子的数据被显示(asdasdasd)。删除标签时,我可以在日志中看到第二个孩子的 ID,尽管它不应该再存在了。第二个孩子 ID 为绿色。

过时的数据

我可能误会了什么。你能帮帮我吗?

标签: javascriptmeteormeteor-blazespacebars

解决方案


我不知道从哪里开始,但有很多错误,我宁愿在这里提供一个运行中的解决方案并附上评论。

首先,该each函数应该正确传递 id 而不是整个孩子,否则find将导致错误:

<template name="parent">
  {{#each child in getChildren}}
    {{#with (childArgs child.id)}}
      {{> childTemplate this}}
    {{/with}}
  {{/each}}
</template>

Template.instance()在帮助器中,您可以通过使用词法作用域来避免调用过多的函数:

childArgs (childId) {
  const instance = Template.instance()
  const children = instance.state.get('children')
  const childData = children.find(child => child.id === childId)
  const value = {
    // Default values
    data: childData,

    // Just adding a child to the reactive variable using callback
    onAddClick () {
      const children = instance.state.get('children')
      const length = children ? children.length : 1
      const newChild = { id: `data ${length}` }
      const newChildren = [...children, newChild]
      instance.state.set('children', newChildren)
    },

    // Just deleting the child in the reactive variable in the callback
    onDeleteClick (childId) {
      const children = instance.state.get('children')
      const childIndex = children.findIndex(child => child.id === childId)
      children.splice(childIndex, 1)
      instance.state.set('children', children)
    }
  }
  return value
}

然后请注意,在'click .delete_row'您使用的事件回调中,templateInstance.data.id但这始终 undefined与您当前的结构一致。应该是templateInstance.data.data.id因为data总是为模板实例中的所有数据定义,如果你命名一个属性data,那么你必须通过以下方式访问它data.data

Template.childTemplate.events({
  'click .add_row' (event, templateInstance) {
    templateInstance.data.onAddClick()
  },
  'click .delete_row' (event, templateInstance) {
    templateInstance.data.onDeleteClick(templateInstance.data.id)
  }
})

现在,为什么您的数据被奇怪地排序也是有道理的。看一下onDeleteClick回调:

onDeleteClick (childId) {
  // childId is always undefined in your code
  const children = instance.state.get('children')
  const childIndex = children.findIndex(child => child.id === childId)
  // childIndex is always -1 in your code
  // next time make a dead switch in such situations:
  if (childIndex === -1) {
    throw new Error(`Expected child by id ${childId}`)
  }
  children.splice(childIndex, 1)
  // if index is -1 this removes the LAST element
  instance.state.set('children', children)
}

所以你的问题是拼接行为并将未经检查的索引传递给拼接:

开始更改数组的索引。如果大于数组的长度,start 将被设置为数组的长度。如果为负数,它将从数组末尾开始那么多元素(原点 -1,意味着 -n 是倒数第 n 个元素的索引,因此等效于 array.length - n 的索引)。如果 array.length + start 小于 0,它将从索引 0 开始。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice


推荐阅读