爷孙组件的事件传递和 vue2 的写法不一样了

$listeners 在 3 中删除了,将事件合并到了 $attrs 中

需要使用 v-bind 递归绑定 $attrs 传递事件而不是 v-on

$attrs 里的事件名会转为驼峰,并且自动加上 on 前缀
例:
@sort 在 $attrs 中的 key 为 onSort
@on-sort 在 $attrs 中的 key onOnSort

emit 里声明的事件,将从 $attrs 里删除,无法继续递归传递

移除$listeners

https://v3-migration.vuejs.org/zh/breaking-changes/listeners-removed.html

$listeners 对象在 Vue 3 中已被移除。事件监听器现在是 $attrs 的一部分:

{
  text: '这是一个 attribute',
  onClose: () => console.log('close 事件被触发')
}

emit 声明的事件会从 $attrs 中删除

其实和 prop 一样了

https://cn.vuejs.org/api/options-state.html#emits

注意,emits 选项会影响一个监听器被解析为组件事件监听器,还是原生 DOM 事件监听器。
被声明为组件事件的监听器不会被透传到组件的根元素上,且将从组件的 $attrs 对象中移除。
详见透传 Attributes。

【vue3】爷孙组件事件传递

<!-- 父组件 -->
<template>
  <div class="parent-root">
    <child @sort="sort"></child>
  </div>
</template>

<!-- 子组件 -->
<template>
  <div class="child-root">
    <!-- 这里 sort 不要写在 emit 声明中 -->
    <grandson v-bind="$attrs"></grandson>
  </div>
</template>

<!-- 孙组件 -->
<template>
  <div class="grandson-root">
    <button @click="$emit('sort')">触发顶级父组件的 sort</button>
  </div>
</template>

【vue3】递归组件事件传递

<!-- 父组件 -->
<template>
  <div class="parent-root">
    <div v-if="children.length">
      <!-- 监听递归组件传递的 sort 事件并触发 -->
      <child 
        v-for="item in children"
        :key="item.id"
        @sort="sort">
      </child>
    </div>
  </div>
</template>

<!-- 子组件 -->
<template>
  <div class="child-root">
    <button @click="$emit('sort')">触发顶级父组件的 sort</button>
    
    <div v-if="children.length">
      <!-- v-bind="$attrs" -->
      <!-- 使事件继续向下递归传递" -->
      <child
        v-for="item in children"
        :key="item.id"
        v-bind="$attrs">
      </child>
    </div>
  </div>
</template>