$emit与作用域插槽核心对比及示例代码

$emit与slot作用域插槽核心对比及示例代码

$emit 和作用域插槽的核心对比 + 完整可运行的示例代码,用最清晰的方式帮你区分这两种传值方式,一眼看懂它们的差异和适用场景。

一、核心对比表

维度 this.$emit(子传父事件) 作用域插槽(Scoped Slot)
数据流方向 子组件 → 父组件(事件+数据) 子组件 → 父组件(仅暴露数据)
核心目的 子组件触发事件,通知父组件处理逻辑 子组件提供数据,父组件自定义渲染内容
使用方式 子组件 $emit('事件名', 数据),父组件 @事件名="方法" 子组件 <slot :数据名="数据">,父组件 v-slot="{数据名}"
耦合度 子组件需提前定义事件,耦合度较高 子组件只提供数据,不关心渲染,耦合度极低
灵活性 父组件只能处理逻辑,无法自定义子组件内容 父组件可完全自定义子组件内的渲染内容
适用场景 表单提交、弹窗关闭、按钮点击等"触发型"场景 表格行操作、列表项自定义渲染等"渲染型"场景

二、完整示例对比

下面用同一个表格需求 ,分别写 $emit 版本和作用域插槽版本,可以直接复制运行,直观感受差异。

场景需求

实现一个表格,每行显示"姓名+操作按钮",点击按钮删除对应行数据。


版本1:使用 $emit 实现(耦合度高)
Plain 复制代码
<!-- 子组件:MyTable-emit.vue -->
<template>
  <table border="1" cellpadding="10">
    <tr>
      <th>姓名</th>
      <th>操作</th>
    </tr>
    <tr v-for="item in data" :key="item.id">
      <td>{{ item.name }}</td>
      <!-- 子组件固定渲染"删除按钮",并触发事件 -->
      <td>
        <button @click="$emit('delete', item.id)">删除</button>
      </td>
    </tr>
  </table>
</template>

<script>
export default {
  props: {
    data: { type: Array, required: true }
  }
}
</script>

<!-- 父组件:App.vue -->
<template>
  <div>
    <h3>$emit 版本(耦合度高)</h3>
    <MyTable-emit :data="list" @delete="handleDelete" />
  </div>
</template>

<script>
import MyTableEmit from './MyTable-emit.vue'
export default {
  components: { MyTableEmit },
  data() {
    return {
      list: [
        { id: 1, name: '张三' },
        { id: 2, name: '李四' },
        { id: 3, name: '王五' }
      ]
    }
  },
  methods: {
    handleDelete(id) {
      // 父组件处理删除逻辑
      this.list = this.list.filter(item => item.id !== id)
    }
  }
}
</script>

缺点:如果想把"删除按钮"改成"编辑按钮",必须修改子组件的代码,子组件和业务逻辑强绑定。


版本2:使用作用域插槽实现(灵活性高)
Plain 复制代码
<!-- 子组件:MyTable-slot.vue -->
<template>
  <table border="1" cellpadding="10">
    <tr>
      <th>姓名</th>
      <th>操作</th>
    </tr>
    <tr v-for="item in data" :key="item.id">
      <td>{{ item.name }}</td>
      <!-- 子组件只暴露数据,不渲染任何按钮 -->
      <td>
        <slot :row="item"></slot>
      </td>
    </tr>
  </table>
</template>

<script>
export default {
  props: {
    data: { type: Array, required: true }
  }
}
</script>

<!-- 父组件:App.vue -->
<template>
  <div>
    <h3>作用域插槽版本(灵活性高)</h3>
    <MyTable-slot :data="list">
      <!-- 父组件自定义操作按钮,直接使用子组件暴露的 row 数据 -->
      <template v-slot="{ row }">
        <!-- 想改按钮类型/样式/逻辑,只改父组件即可,子组件无需变动 -->
        <button @click="handleDelete(row.id)" style="margin-right: 5px;">删除</button>
        <button @click="handleEdit(row)">编辑</button>
      </template>
    </MyTable-slot>
  </div>
</template>

<script>
import MyTableSlot from './MyTable-slot.vue'
export default {
  components: { MyTableSlot },
  data() {
    return {
      list: [
        { id: 1, name: '张三' },
        { id: 2, name: '李四' },
        { id: 3, name: '王五' }
      ]
    }
  },
  methods: {
    handleDelete(id) {
      this.list = this.list.filter(item => item.id !== id)
    },
    handleEdit(row) {
      alert(`编辑:${row.name}`)
    }
  }
}
</script>

优点:子组件完全通用,父组件可以自由定义操作按钮(删除、编辑、查看等),甚至修改按钮样式、添加新按钮,无需改动子组件一行代码。

三、总结

  1. $emit ** 是"事件驱动"**:子组件触发事件,父组件响应处理,适合"触发逻辑"的场景,耦合度较高;

  2. 作用域插槽是"数据暴露":子组件只提供数据,父组件自定义渲染,适合"自定义内容"的场景,灵活性和复用性更高;

  3. 选型原则 :如果需要父组件自定义子组件内的内容 → 用作用域插槽;如果只是子组件通知父组件做某事 → 用 $emit

相关推荐
白日梦想家68120 小时前
定时器实战避坑+高级用法,从入门到精通
开发语言·前端·javascript
遗憾随她而去.21 小时前
前端 Loadsh 经常使用的方法总结
前端
是宇写的啊21 小时前
MyBaties
java·开发语言·mybatis
Csvn21 小时前
前端安全加固:XSS、CSRF、CSP 防护实战
前端
钝挫力PROGRAMER21 小时前
程序中事件机制的实现
java·后端·python·软件工程
洛洛呀。21 小时前
Kali系统桥接模式下相关网络故障
linux·服务器·桥接模式
程序员威哥21 小时前
Java调用YOLO模型性能优化实战:CPU/GPU加速与内存优化全指南
java·人工智能·后端
momo(激进版)21 小时前
mathjs使用简记
前端·javascript
JarvanMo21 小时前
7 个开源 iOS 应用,让你成为更好的开发者
前端·ios
Xpower 1721 小时前
OpenClaw Token 优化的技术方案与实践:OpenSpace 自进化 Skill 引擎
java·开发语言·人工智能