$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

相关推荐
mCell7 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell8 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭8 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清8 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
银烛木8 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076608 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声8 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易8 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
小白同学_C8 小时前
Lab4-Lab: traps && MIT6.1810操作系统工程【持续更新】 _
linux·c/c++·操作系统os
今天只学一颗糖8 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构