Vue 3 - 插槽上的独立事件传播

Vue 3 - Independent event propagation on slots

今天我有一个关于 Vue 的棘手问题,涉及槽和事件传播。 Here is a codesample

一些背景信息:我有一个订单,有n个订单仓位。这些订单及其头寸显示在不同的页面上。为了实现一致的 UI,我为 Order.vuePosition.vue 创建了 vue 组件。这些组件不包含任何业务逻辑,只负责在我的应用程序中保持一致的样式。

此外,我还有一个订单详情视图,其中显示了我的订单信息及其仓位。

此外,我还有另一个视图,其中显示相同的信息并且应该可以 select 一些 order/positions 取消。

数据包含在页面组件中并作为道具传递给 order/position。对于取消页面,我在插槽中传递复选框组件:

// OrderCancellationPage.vue
<Order v-for="order in orders" :key="order.id" :id="order.id" :positions="order.positions">
  <InvoiceCancellationCheckbox :order-id="order.id" @isCancelled="onCancelledEvent($event)" />
</Order>
// Order.vue
<div>
  <h3>Order: # {{ id }}</h3>
  <slot></slot>
</div>
<OrderPosition
  v-for="position in positions"
  :key="position.posIndex"
  :pos-index="position.posIndex"
>
  <InvoiceCancellationCheckbox :orderId="id" :posIndex="position.posIndex"       @isCancelled="$emit('isCancelled', $event)"/> //propagate the event to order
</OrderPosition>
// OrderPosition.vue
<div>
  <span>Position {{ posIndex }}</span>
  <slot></slot>
</div>

我想实现,我的 order/positions 组件不包含任何取消逻辑。 因此,InvoiceCancellationCheckbox 正在发出一个事件,如果它已被检查。 OrderPosition 正在使用 @isCancelled=... 监听此事件,该事件已在我的插槽组件中声明。最后,OrderCancellationPage 应该监听所有这些事件并存储 selected 订单及其位置。

问题: 我收到此警告:

[Vue warn]: Extraneous non-emits event listeners (isCancelled) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the "emits" option. 
  at <Order key="2021-9064-9333-9803" id="2021-9064-9333-9803" positions= 

我可以解决这个警告,如果我将 emits: ['isCancelled'] 添加到 Order。但是,比订单加上我不想要的取消复选框。还有其他解决方案吗?

编辑:该示例似乎不适用于 stackblitz,但它适用于本地计算机。我在这段代码中没有看到任何错误...


v-model 挽救了局面。

我用解决方案分叉了旧代码:https://stackblitz.com/edit/vue-t6ekts

基本上是关于使用 v-model 作为复选框:

<template>
  <span>
    <input type="checkbox" :id="id" v-model="modelValue" />
    <label :for="id">Select for cancellation</label>
  </span>
</template>

<script>
  ...
  props: {
    modelValue: Boolean,
    orderId: String,
    posIndex: Number
  },
  emits: ['update:modelValue'],
  watch: {
    modelValue() {
      this.$emit('update:modelValue', this.modelValue);
    },
  },

并将其作为插槽传递:

<Order v-for="order in orders" :key="order.id" :id="order.id" :order="order">
  <CancellationCheckbox :order-id="order.id" v-model="order.isCancelled"/>
</Order>
<OrderPosition v-for="position in order.positions" :key="position.posIndex" :position="position">
  <CancellationCheckbox :orderId="order.id" :posIndex="position.posIndex" v-model="position.isCancelled"/>
</OrderPosition>