学习vue第十五天 子组件传递父组件(Emit事件)

用最直白、最详细的方式给你讲解子组件传递父组件(Emit事件)!

📞 Emit:子组件给父组件打电话

🎭 最简单的比喻

想象你是儿子 (子组件),你的爸爸(父组件)在楼下:

复制代码
👦 儿子(子组件)在楼上
  ↓ 喊话:"爸爸,我饿了!"
👨 爸爸(父组件)在楼下
  ↓ 听到后去做饭

📱 第一步:最简单的例子

子组件:发送消息(打电话)

vue 复制代码
<!-- Son.vue(儿子) -->
<template>
  <div>
    <button @click="callDad">我饿了,叫爸爸</button>
  </div>
</template>

<script>
export default {
  methods: {
    callDad() {
      // 📞 给爸爸打电话(触发事件)
      this.$emit('hungry')
      console.log('儿子:爸爸,我饿了!')
    }
  }
}
</script>

父组件:接收消息(接电话)

vue 复制代码
<!-- App.vue(爸爸) -->
<template>
  <div>
    <!-- 📞 监听儿子的电话 -->
    <Son @hungry="makeDinner" />
  </div>
</template>

<script>
import Son from './Son.vue'

export default {
  components: { Son },
  methods: {
    makeDinner() {
      console.log('爸爸:好的,我去做饭!')
      alert('爸爸开始做饭了')
    }
  }
}
</script>

流程

  1. 儿子点击按钮
  2. 儿子调用this.$emit('hungry')(打电话)
  3. 爸爸监听到@hungry事件(接电话)
  4. 爸爸执行makeDinner方法(去做饭)

📦 第二步:传递数据

子组件:打电话时说具体内容

vue 复制代码
<!-- Son.vue -->
<template>
  <div>
    <button @click="orderFood('汉堡')">我想吃汉堡</button>
    <button @click="orderFood('披萨')">我想吃披萨</button>
    <button @click="orderFood('炸鸡')">我想吃炸鸡</button>
  </div>
</template>

<script>
export default {
  methods: {
    orderFood(foodName) {
      // 📞 打电话并告诉爸爸想吃什么
      this.$emit('order', foodName)
      console.log(`儿子:爸爸,我想吃${foodName}!`)
    }
  }
}
</script>

父组件:接电话并知道具体内容

vue 复制代码
<!-- App.vue -->
<template>
  <div>
    <h2>今天吃:{{ todayFood }}</h2>
    <!-- 📞 监听order事件,接收foodName参数 -->
    <Son @order="handleOrder" />
  </div>
</template>

<script>
import Son from './Son.vue'

export default {
  components: { Son },
  data() {
    return {
      todayFood: ''
    }
  },
  methods: {
    handleOrder(foodName) {
      console.log(`爸爸:好的,我去买${foodName}!`)
      this.todayFood = foodName
    }
  }
}
</script>

流程

  1. 儿子点击"我想吃汉堡"
  2. 儿子调用this.$emit('order', '汉堡')
  3. 爸爸的handleOrder方法接收到参数'汉堡'
  4. 爸爸更新todayFood = '汉堡'

🎯 第三步:传递多个参数

vue 复制代码
<!-- Son.vue -->
<template>
  <div>
    <button @click="orderMeal">点餐</button>
  </div>
</template>

<script>
export default {
  methods: {
    orderMeal() {
      // 📞 可以传递多个参数
      this.$emit('order', '汉堡', 2, '加辣')
      //           事件名   参数1  参数2  参数3
    }
  }
}
</script>
vue 复制代码
<!-- App.vue -->
<template>
  <Son @order="handleOrder" />
</template>

<script>
export default {
  methods: {
    handleOrder(food, quantity, note) {
      console.log(`食物:${food}`)      // "汉堡"
      console.log(`数量:${quantity}`)  // 2
      console.log(`备注:${note}`)      // "加辣"
    }
  }
}
</script>

🔧 第四步:声明Emits(推荐写法)

为什么要声明?

就像打电话前要告诉别人"我可能会给你打这些类型的电话"

vue 复制代码
<!-- Son.vue -->
<script>
export default {
  // 📋 声明:我可能会触发这些事件
  emits: ['hungry', 'order', 'sleep'],
  
  methods: {
    callDad() {
      this.$emit('hungry')
    },
    orderFood(food) {
      this.$emit('order', food)
    },
    goToSleep() {
      this.$emit('sleep')
    }
  }
}
</script>

带验证的声明

vue 复制代码
<script>
export default {
  // 🔍 对象形式:可以验证参数
  emits: {
    // 简单声明
    hungry: null,
    
    // 带验证
    order(food) {
      if (!food) {
        console.warn('必须告诉我想吃什么!')
        return false
      }
      return true
    },
    
    // 验证多个参数
    addToCart(product, quantity) {
      if (quantity <= 0) {
        console.warn('数量必须大于0!')
        return false
      }
      return true
    }
  }
}
</script>

🎪 完整实战案例:计数器

子组件:操作按钮

vue 复制代码
<!-- CounterOperation.vue -->
<template>
  <div class="counter-operations">
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
    <button @click="addTen">+10</button>
    <button @click="reset">重置</button>
  </div>
</template>

<script>
export default {
  // 📋 声明可以触发的事件
  emits: ['add', 'sub', 'addN', 'reset'],
  
  methods: {
    increment() {
      // 📞 通知父组件:加1
      this.$emit('add')
    },
    
    decrement() {
      // 📞 通知父组件:减1
      this.$emit('sub')
    },
    
    addTen() {
      // 📞 通知父组件:加10
      this.$emit('addN', 10)
    },
    
    reset() {
      // 📞 通知父组件:重置
      this.$emit('reset')
    }
  }
}
</script>

<style scoped>
button {
  margin: 5px;
  padding: 10px 20px;
  font-size: 16px;
}
</style>

父组件:处理事件

vue 复制代码
<!-- App.vue -->
<template>
  <div class="app">
    <h1>计数器</h1>
    <h2>当前计数:{{ counter }}</h2>
    
    <!-- 📞 监听子组件的所有事件 -->
    <CounterOperation
      @add="addOne"
      @sub="subOne"
      @addN="addNNum"
      @reset="resetCounter"
    />
  </div>
</template>

<script>
import CounterOperation from './CounterOperation.vue'

export default {
  components: { CounterOperation },
  
  data() {
    return {
      counter: 0
    }
  },
  
  methods: {
    addOne() {
      this.counter++
      console.log('加1,当前:', this.counter)
    },
    
    subOne() {
      this.counter--
      console.log('减1,当前:', this.counter)
    },
    
    addNNum(num) {
      this.counter += num
      console.log(`加${num},当前:`, this.counter)
    },
    
    resetCounter() {
      this.counter = 0
      console.log('重置为0')
    }
  }
}
</script>

🎯 实战案例2:商品列表

子组件:商品卡片

vue 复制代码
<!-- ProductCard.vue -->
<template>
  <div class="product-card">
    <img :src="product.image" :alt="product.name">
    <h3>{{ product.name }}</h3>
    <p class="price">¥{{ product.price }}</p>
    
    <div class="actions">
      <!-- 📞 点击时通知父组件 -->
      <button @click="handleAddToCart">加入购物车</button>
      <button @click="handleBuyNow">立即购买</button>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    product: {
      type: Object,
      required: true
    }
  },
  
  // 📋 声明事件
  emits: ['add-to-cart', 'buy-now'],
  
  methods: {
    handleAddToCart() {
      // 📞 通知父组件:加入购物车
      this.$emit('add-to-cart', this.product)
      console.log('子组件:通知父组件加入购物车', this.product.name)
    },
    
    handleBuyNow() {
      // 📞 通知父组件:立即购买
      this.$emit('buy-now', this.product)
      console.log('子组件:通知父组件立即购买', this.product.name)
    }
  }
}
</script>

父组件:处理购物逻辑

vue 复制代码
<!-- App.vue -->
<template>
  <div class="app">
    <h1>商品列表</h1>
    
    <div class="product-list">
      <ProductCard
        v-for="product in products"
        :key="product.id"
        :product="product"
        @add-to-cart="addToCart"
        @buy-now="buyNow"
      />
    </div>
    
    <div class="cart">
      <h2>购物车({{ cartCount }}件)</h2>
    </div>
  </div>
</template>

<script>
import ProductCard from './ProductCard.vue'

export default {
  components: { ProductCard },
  
  data() {
    return {
      products: [
        { id: 1, name: 'iPhone 15', price: 5999, image: 'iphone.jpg' },
        { id: 2, name: 'MacBook Pro', price: 12999, image: 'macbook.jpg' },
        { id: 3, name: 'AirPods', price: 1299, image: 'airpods.jpg' }
      ],
      cart: []
    }
  },
  
  computed: {
    cartCount() {
      return this.cart.length
    }
  },
  
  methods: {
    addToCart(product) {
      console.log('父组件:收到加入购物车请求', product.name)
      this.cart.push(product)
      alert(`${product.name} 已加入购物车!`)
    },
    
    buyNow(product) {
      console.log('父组件:收到立即购买请求', product.name)
      alert(`正在购买 ${product.name},价格:¥${product.price}`)
    }
  }
}
</script>

🎭 Emit的两种写法对比

写法1:在模板中直接写表达式

vue 复制代码
<!-- 子组件 -->
<template>
  <!-- 直接在模板中触发事件 -->
  <button @click="$emit('add')">+1</button>
  <button @click="$emit('addN', 10)">+10</button>
</template>

<script>
export default {
  emits: ['add', 'addN']
}
</script>

写法2:在methods中触发(推荐)

vue 复制代码
<!-- 子组件 -->
<template>
  <button @click="handleAdd">+1</button>
  <button @click="handleAddTen">+10</button>
</template>

<script>
export default {
  emits: ['add', 'addN'],
  
  methods: {
    handleAdd() {
      // 可以在这里做一些逻辑处理
      console.log('准备加1')
      this.$emit('add')
    },
    
    handleAddTen() {
      // 可以在这里做一些逻辑处理
      console.log('准备加10')
      this.$emit('addN', 10)
    }
  }
}
</script>

🔍 深入理解:事件流程

完整流程图

复制代码
1. 用户点击子组件的按钮
   ↓
2. 子组件执行方法
   ↓
3. 方法中调用 this.$emit('事件名', 参数)
   ↓
4. Vue检测到emit
   ↓
5. Vue查找父组件中是否监听了这个事件(@事件名)
   ↓
6. 如果有监听,执行父组件的方法
   ↓
7. 父组件方法接收参数并处理

代码流程

vue 复制代码
<!-- 子组件 -->
<template>
  <button @click="sendMessage">发送</button>
</template>

<script>
export default {
  methods: {
    sendMessage() {
      // 1️⃣ 子组件触发事件
      this.$emit('message', 'Hello')
      console.log('1. 子组件:发送消息')
    }
  }
}
</script>
vue 复制代码
<!-- 父组件 -->
<template>
  <Child @message="receiveMessage" />
</template>

<script>
export default {
  methods: {
    receiveMessage(msg) {
      // 2️⃣ 父组件接收事件
      console.log('2. 父组件:收到消息', msg)
    }
  }
}
</script>

控制台输出

复制代码
1. 子组件:发送消息
2. 父组件:收到消息 Hello

⚠️ 注意事项

🚫 错误1:事件名大小写

vue 复制代码
<!-- ❌ 错误:使用驼峰命名 -->
<Child @addToCart="handleAdd" />

<!-- 子组件 -->
this.$emit('addToCart')  // 可能不工作

<!-- ✅ 正确:使用kebab-case -->
<Child @add-to-cart="handleAdd" />

<!-- 子组件 -->
this.$emit('add-to-cart')  // 推荐

🚫 错误2:忘记在父组件监听

vue 复制代码
<!-- ❌ 错误:子组件emit了,但父组件没监听 -->
<Child />  <!-- 没有@事件名 -->

<!-- 子组件 -->
this.$emit('message')  // 发送了,但没人接收

<!-- ✅ 正确:父组件要监听 -->
<Child @message="handleMessage" />

🚫 错误3:参数接收错误

vue 复制代码
<!-- 子组件发送多个参数 -->
this.$emit('order', '汉堡', 2, '加辣')

<!-- ❌ 错误:只接收一个参数 -->
methods: {
  handleOrder(food) {
    // 只能拿到'汉堡',拿不到2和'加辣'
  }
}

<!-- ✅ 正确:接收所有参数 -->
methods: {
  handleOrder(food, quantity, note) {
    console.log(food, quantity, note)
  }
}

💡 记忆口诀

复制代码
子传父用emit,
打电话通知你。
父组件要监听,
@符号加事件名。
参数可以传多个,
父组件按顺序收。

🎯 总结

Emit就像打电话

步骤 子组件(打电话) 父组件(接电话)
1 声明emits 准备监听 @事件名
2 触发 this.$emit('事件名', 参数) 定义处理方法
3 发送消息和数据 接收消息和数据
4 完成通知 处理业务逻辑

关键点

  1. 子组件 :用this.$emit('事件名', 参数)发送消息
  2. 父组件 :用@事件名="方法"监听消息
  3. 参数传递:可以传递多个参数
  4. 事件命名:推荐使用kebab-case(短横线命名)
  5. 声明emits:推荐在子组件中声明可触发的事件

现在明白了吗?子传父就是子组件通过emit"打电话"通知父组件,父组件监听"接电话"并处理!😊

相关推荐
Byron070710 小时前
从 0 到 1 搭建 Vue 前端工程化体系:提效、提质、降本实战落地
前端·javascript·vue.js
慎独41310 小时前
科学赋能,让孩子专注高效爱上学习
学习
LGL6030A10 小时前
Java学习历程26——线程安全
java·开发语言·学习
zhengfei61110 小时前
【AI平台】- 基于大模型的知识库与知识图谱智能体开发平台
vue.js·语言模型·langchain·知识图谱·多分类
徐小夕@趣谈前端11 小时前
Web文档的“Office时刻“:jitword共建版2.0发布!让浏览器变成本地生产力
前端·数据结构·vue.js·算法·开源·编辑器·es6
德育处主任Pro11 小时前
纯前端网格路径规划:PathFinding.js的使用方法
开发语言·前端·javascript
墨笔.丹青11 小时前
基于QtQuick开发界面设计出简易的HarmonyUI界面----下
开发语言·前端·javascript
学历真的很重要11 小时前
【系统架构师】第二章 操作系统知识 - 第二部分:进程管理(详解版)
学习·职场和发展·系统架构·系统架构师
董世昌4111 小时前
深度解析浅拷贝与深拷贝:底层逻辑、实现方式及实战避坑
前端·javascript·vue.js
Nebula_g11 小时前
线程进阶: 无人机自动防空平台开发教程(更新)
java·开发语言·数据结构·学习·算法·无人机