$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

相关推荐
是糖糖啊19 分钟前
OpenClaw 从零到一实战指南(飞书接入)
前端·人工智能·后端
Despupilles26 分钟前
第三篇、基本骨架结构
前端
swipe40 分钟前
从原理到手写:彻底吃透 call / apply / bind 与 arguments 的底层逻辑
前端·javascript·面试
踩着两条虫42 分钟前
从设计稿到代码:VTJ.PRO 的 AI 集成系统架构解析
前端·vue.js·人工智能
Mapmost44 分钟前
从“雕琢”到“生成”:AIGC正在重塑数字孪生世界
前端
后端AI实验室1 小时前
用AI写代码,我差点把漏洞发上线:血泪总结的10个教训
java·ai
掘金一周1 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了 | 掘金一周 3.5
前端·人工智能·agent
JasonYin1 小时前
ViewModel 知识体系思维导图
前端
幸福小宝1 小时前
uniapp 抽屉实现左滑
前端
戳气球的爱玛镇皇后2 小时前
BroadcastChannel 使用总结
前端