试图通过使用 $emit 来避免改变 3 级深度的道具

Trying to avoid mutating a prop that goes 3 levels deep by using $emit

编辑:Here's a repo我做了更容易的解析。

我有一个在数据table 中列出产品的组件。 table 的第一列是一个 link,它显示了一个带有被点击产品形式的模式(使用其 ID)。我将 PrimeVue 库用于样式和组件。

<template>
   <Column field="id" headerStyle="width: 5%">
     <template #body="slotProps">
        <ProductForm :product="slotProps.data" :show="showModal(slotProps.data.id)" />
           <a href="#" @click.stop="toggleModal(slotProps.data.id)">
              <span class="pi pi-external-link"> </span>
           </a>
     </template>
   </Column>
</template>

<script>
import ProductForm from "./forms/ProductForm";

export default {
  data() {
     return {
       activeModal: 0,
     }
  },
  components: { ProductForm },
  methods: {
    toggleModal: function (id) {
      if (this.activeModal !== 0) {
        this.activeModal = 0;
        return false;
      }
      this.activeModal = id;
    },
    showModal: function (id) {
      return this.activeModal === id;
    },
  },
</script>

模态框实际上是 ProductForm 组件的一个子组件(我制作了一个模态框模板,这样我就可以重用它)。所以它是 3 个组件(ProductList -> ProductForm -> BaseModal)。这是产品形式:

<template>
  <div>
    <BaseModal :show="show" :header="product.name">
      <span class="p-float-label">
        <InputText id="name" type="text" :value="product.name" />
        <label for="name">Product</label>
      </span>
    </BaseModal>
  </div>
</template>

<script>
import BaseModal from "../_modals/BaseModal";

export default {
  props: ["product", "show"],
  components: { BaseModal },
  data() {
    return {};
  },
};
</script>

当模式弹出时,它使用 ProductForm 子组件。这是 BaseModal 组件:

<template>
  <div>
    <Dialog :header="header" :visible.sync="show" :modal="true" :closable="true" @hide="doit">
      <slot />
    </Dialog>
  </div>
</template>

<script>
export default {
  props: {
    show: Boolean,
    header: String,
  },
  methods: {
    doit: function () {
      let currentShow = this.show;
      this.$emit("showModel", currentShow)
    },
  },
  data() {
    return {
    };
  },
};
</script>

我正在传递 product 对象和一个 show 布尔值,它指定模态是否可见,从第一个组件 (ProductList) 一直到 ProductForm 组件,然后最后是 BaseModal 组件。模态是 PrimeVue component called Dialog。该组件实际上有自己的 属性,称为“closable”,它在单击时使用 X 按钮关闭模式,该按钮与名为 hide 的事件相关联。一切实际上都有效。我可以打开模式并关闭它。出于某种原因,我必须在初始后打开之前单击另一个模式 link 两次。

问题是当我关闭模式时,出现 Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "show" 错误。我已经尝试了一切以发出事件并更改那里的原始道具值,但错误仍然存​​在(即使来自上面的代码)但我不确定是否因为我有 3 个组件深度它不会工作。我对使用 props 和 slots 以及 $emit 还很陌生,所以我知道我做错了什么。我还不熟悉这么深的组件布局,所以我什至可能无法正确完成整个布局。我错过了什么?

好吧,你正在从 BaseModal 发出 showModel 事件,但你没有在 parent 上监听它并转发它+在 grandparent 上监听(ProductForm)

但主要问题是 BaseModal 中的 :visible.sync="show"。这与您执行 :visible="show" @update:visible="show = $event" (docs) 相同。因此,当 Dialog 关闭时,PrimeVue 发出 update:visible 事件,该事件由 BaseModal 组件选择(感谢 .sync 修饰符)并导致 show prop inside BaseModal 和错误信息...

记住 永远不要直接使用属性值 v-model.sync

要修复它,请通过 computed with the setter:

间接使用道具

BaseModal

<template>
  <div>
    <Dialog :header="header" :visible.sync="computedVisible" :modal="true" :closable="true">
      <slot />
    </Dialog>
  </div>
</template>

<script>
export default {
  props: {
    show: Boolean,
    header: String,
  },
  computed: {
    computedVisible: {
      get() { return this.show },
      set(value) { this.$emit('update:show', value) }
    }
  },
};
</script>

现在您可以将相同的计算添加到您的 ProductForm 组件中并将模板更改为 <BaseModal :show.sync="computedVisible" :header="product.name">(因此当 ProductForm 接收到 update:show 事件时,它将发出与 parent 相同的事件 - 这是必需的,因为 Vue 事件不会像 DOM 事件那样“冒泡”,只有即时 parent 组件接收事件)

最后一步是处理 ProductList 中的 update:show<ProductForm :product="slotProps.data" :show="showModal(slotProps.data.id)" @update:show="toggleModal(slotProps.data.id)"/>