VUE:如果子项发出事件,则在父项中进行更改

VUE: make changes in parent if child emits event

具有以下 vue 结构:

<!-- Parent Component -->
    <div v-for="item in items">
       <h1>{{item.title}}</h1>
        <!-- this is my target button --> 
       <button></button>
       <child :parentid="item.id"/>
    </div>

<!-- Child Component-->
    <div v-for="subitem in subitems">
      <h2>{{subitem.title}}</h2>
       <button v-on:click="delete(subitem.id)" v-on:update-parent-button="howToDo()" >delete</button>
    </div>

如果 subitems 长度为 0,我想向父级发出一个事件以禁用父级按钮。子组件中的子项在每次删除时都会刷新,我会像这样观察这种状态变化:

watch: {
     subitems(newValue, oldValue) {
                this.$emit('update-parent-button', ('test', newValue.length))
            }
        },

我怎样才能做到这一点而不需要太多努力来定位正确的父元素

首先,我认为一个例子可以满足您的要求:

const Child = {
  template: `
    <div>
      <div v-for="subitem in subitems">
        <h2>{{ subitem.title }}</h2>
        <button @click="deleteItem(subitem.id)">delete</button>
      </div>
    </div>
  `,
  
  props: ['parentid'],
  
  data () {
    return {
      subitems: [
        { title: 'Subitem a', id: 6 },
        { title: 'Subitem b', id: 7 }
      ]
    }
  },
  
  methods: {
    deleteItem (id) {
      this.subitems = this.subitems.filter(item => item.id !== id)
    }
  },
  
  watch: {
    'subitems.length': {
      immediate: true,
      
      handler (newLength) {
        this.$emit('update-subitem-count', {
          id: this.parentid,
          count: newLength
        })
      }
    }
  }
}

new Vue({
  el: '#app',
  
  components: {
    Child
  },
  
  data () {
    return {
      hasSubitems: {},
      items: [
        { title: 'Item A', id: 1 },
        { title: 'Item B', id: 2 },
        { title: 'Item C', id: 3 }
      ]
    }
  },
  
  methods: {
    onSubitemChange ({ count, id }) {
      this.$set(this.hasSubitems, id, count > 0)
    }
  }
})
<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>

<div id="app">
  <div v-for="item in items">
    <h1>{{ item.title }}</h1>
    <button :disabled="!hasSubitems[item.id]">Target button</button>
    <child
      :parentid="item.id"
      @update-subitem-count="onSubitemChange"
    ></child>
  </div>
</div>

我尽量使结构与问题中的代码相似。

备注:

  1. 我在父组件中添加了一个名为 hasSubitems 的对象。这用作从项目 ID 到确定是否应启用该按钮的布尔值的映射。还有许多其他方法可以做到这一点。相反,布尔值可以存储为 item 的 属性。或者,可以引入一个新组件来保存 v-for 的内容,并且该组件可以保存相关状态。一般来说,如果你有一个不平凡的 v-for ,那么值得考虑引入一个新的组件并只循环遍历它。
  2. 我已将 watch 更改为 subitems.length 而不是 subitems。这不是必需的,但它似乎更针对我们实际关心的数量。
  3. 我已将 parentid 包含在发出的事件中。这也不是必需的,但它使在父组件中编写侦听器代码稍微容易一些。否则我们将不得不从 listener 属性中的 v-for item 中获取该 id。
  4. 原代码有一个奇怪的括号表达式('test', newValue.length)。不确定它试图达到什么目的,但我怀疑它是否符合您的期望。
  5. 我在 watch 上使用了 immediate,这样它会立即触发,确保按钮最初处于正确状态。如果您无论如何都异步加载初始列表,那可能不相关。
  6. 我用 $set 更新了 hasSubitems。这是必需的,因为正在向对象添加新属性,否则 Vue 无法检测到这些属性。另一种方法是确保 hasSubitems 在首次创建时正确填充所有属性。这可能取决于 items 来自哪里。

除此之外,我要指出的是,以这种方式使用事件会引起滥用,并表明您的设计中存在更根本的缺陷。从所提供的信息很难说那可能是什么。但是,我会尝试提出建议。

正如我之前提到的,每当您有一个重要的 v-for 时,都值得考虑引入一个新组件。所以你可能要考虑把父组件模板改成这样:

<my-item-component
  v-for="item in items"
  :item="item"
/>

然后您将引入一个名为 my-item-component 的组件。该组件将包装 <h1><button><child> 组件。正如我之前提到的,这样的组件可以保存 <button> 是否应该被禁用的状态。这种状态可以保存在一个简单的布尔值 属性 中。但除此之外,my-item-component 承担加载和操作子项的责任可能更有意义。

也可以使用插槽来解决您的问题。当数据似乎以错误的方式流动时,这可能暗示您需要一个插槽,或者更具体地说是一个作用域插槽。

确切地找出哪些责任应该放在什么地方可能会很棘手,而且问题中没有足够的信息来确定在您的场景中最好的方法是什么。但是,奇怪地使用事件在组件之间进行通信通常表明您以稍微错误的方式划分了您的关注点,并且责任可能需要转移。