当前位置: 首页 > news >正文

Vue组件通信保姆级案例教程:每个方法一个例子,看完直接上手用

Vue组件通信保姆级案例教程:每个方法一个例子,看完直接上手用

之前我们聊了Vue的基础、路由和状态管理。今天要解决的,是你一旦开始用组件写页面,就一定会碰上的问题:组件之间怎么传数据、怎么互相通知?

很多新手在这里卡住,因为Vue提供了好几种通信方式,不知道该用哪个。今天我就用一个一个独立的小案例,把每种方式讲清楚。每个案例都是一个完整的小场景,代码全部有注释,你照着抄一遍就能跑起来。


案例一:父传子 Props——展示用户卡片

场景:父组件里有一份用户信息(姓名、年龄),子组件负责把这张卡片画出来。

1. 父组件 App.vue

vue

<template> <div class="app"> <h1>父组件</h1> <!-- 使用子组件,并传递两个属性:userName 和 userAge --> <!-- 注意:userAge 前面有冒号,表示传的是数字 25;不加冒号会被当成字符串 "25" --> <UserCard :user-name="name" :user-age="25" /> </div> </template> <script setup> // 引入子组件 import UserCard from './components/UserCard.vue' // ref 用来创建一个响应式数据 import { ref } from 'vue' // 父组件自己的数据:姓名 const name = ref('小明') </script>

2. 子组件 UserCard.vue

vue

<template> <div class="user-card"> <h3>用户卡片</h3> <!-- 在模板中直接使用 props 里声明的变量名 --> <p>姓名:{{ userName }}</p> <p>年龄:{{ userAge }}</p> </div> </template> <script setup> // defineProps 是一个内置的宏,用来声明子组件可以接收哪些属性 // 属性名从父组件传过来的 kebab-case (短横线) 会自动转成 camelCase (驼峰) const props = defineProps({ userName: { type: String, // 类型校验:必须是字符串 required: true // 这个属性必须传,否则控制台会警告 }, userAge: { type: Number, // 类型校验:必须是数字 default: 18 // 如果父组件没传,就用默认值 18 } }) // 在 JavaScript 里使用 props 时,用 props.xxx console.log('接收到的姓名:', props.userName) console.log('接收到的年龄:', props.userAge) </script>

案例小结:Props 是单向的,数据只能从父组件流向子组件。子组件不能直接修改props 的值,要改只能让父组件改。


案例二:子传父 Emit——点赞按钮

场景:子组件是一个点赞按钮,用户点一下,父组件统计的总点赞数加一。

1. 父组件 App.vue

vue

<template> <div> <h2>当前总点赞数:{{ likeCount }}</h2> <!-- 监听子组件发出的 "like" 事件,一旦触发就调用 handleLike 方法 --> <LikeButton @like="handleLike" /> </div> </template> <script setup> import { ref } from 'vue' import LikeButton from './components/LikeButton.vue' // 点赞数初始化为 0 const likeCount = ref(0) // 处理点赞事件的方法 function handleLike() { likeCount.value++ // 点赞数加 1 } </script>

2. 子组件 LikeButton.vue

vue

<template> <div> <!-- 点击按钮时,调用 onLike 函数 --> <button @click="onLike">❤️ 点赞</button> </div> </template> <script setup> // defineEmits 用来声明这个组件可以发出哪些事件,返回一个 emit 函数 const emit = defineEmits(['like']) // 点击后,触发 'like' 事件,父组件就能收到 function onLike() { // 这里还可以传参数,比如 emit('like', '参数内容') emit('like') } </script>

案例小结:子传父的核心就是 emit。子组件声明事件并触发,父组件在模板里用@事件名监听。


案例三:v-model 双向绑定——自定义输入框

场景:封装一个输入框组件,父组件用 v-model 就能直接拿到输入的内容。

1. 父组件 App.vue

vue

<template> <div> <!-- v-model 绑定到 inputText,跟原生的 input 一样好用 --> <MyInput v-model="inputText" /> <p>你输入的内容:{{ inputText }}</p> </div> </template> <script setup> import { ref } from 'vue' import MyInput from './components/MyInput.vue' // 用来存放输入框的内容 const inputText = ref('') </script>

2. 子组件 MyInput.vue

vue

<template> <div> <!-- :value="modelValue" 将接收到的值显示在输入框中 @input 事件触发时,执行更新操作 --> <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" placeholder="请输入内容" /> </div> </template> <script setup> // v-model 默认传过来的 prop 名字必须是 modelValue defineProps({ modelValue: { type: String, default: '' } }) // v-model 默认监听的事件名必须是 update:modelValue defineEmits(['update:modelValue']) </script>

案例小结:v-model 本质上是:modelValue+@update:modelValue的组合。你也可以用v-model:xxx传多个值,原理一样。


案例四:兄弟组件通信——搜索框与列表联动

场景:一个搜索框组件,一个列表组件。在搜索框输入关键词,列表组件根据关键词筛选显示内容。它们没有父子关系,是兄弟。

解决方法:借助它们共同的父组件作为“中转站”。

1. 父组件 App.vue(中转站)

vue

<template> <div> <!-- 搜索框组件:输入时发出 search 事件,携带输入的内容 --> <SearchBox @search="handleSearch" /> <!-- 列表组件:接收 filterKeyword 作为筛选条件 --> <ContentList :keyword="filterKeyword" /> </div> </template> <script setup> import { ref } from 'vue' import SearchBox from './components/SearchBox.vue' import ContentList from './components/ContentList.vue' // 父组件维护一个关键词 const filterKeyword = ref('') // 当搜索框发出 search 事件时,更新关键词 function handleSearch(keyword) { filterKeyword.value = keyword } </script>

2. 搜索框组件 SearchBox.vue

vue

<template> <div> <input :value="keyword" @input="onInput" placeholder="请输入搜索词" /> </div> </template> <script setup> import { ref } from 'vue' const keyword = ref('') const emit = defineEmits(['search']) function onInput(e) { keyword.value = e.target.value // 每次输入内容变化,就发出 search 事件,把内容传给父组件 emit('search', keyword.value) } </script>

3. 列表组件 ContentList.vue

vue

<template> <div> <ul> <!-- 遍历筛选后的列表并显示 --> <li v-for="item in filteredList" :key="item">{{ item }}</li> </ul> </div> </template> <script setup> import { computed } from 'vue' // 接收父组件传来的 keyword const props = defineProps({ keyword: { type: String, default: '' } }) // 假数据 const allItems = ['苹果', '香蕉', '橘子', '葡萄', '西瓜'] // 计算属性:根据 keyword 筛选数据 const filteredList = computed(() => { // 如果没输入关键词,就显示全部 if (!props.keyword) return allItems // 否则,返回名字里包含关键词的项 return allItems.filter(item => item.includes(props.keyword)) }) </script>

案例小结:兄弟组件通信,说白了就是把一个组件的 emit 和另一个组件的 props 结合起来,通过父组件中转一下。


案例五:跨级通信 provide/inject——一键切换主题

场景:顶层组件提供一个“主题色”,所有子孙组件都能直接拿到,不用一层层通过 props 往下传。

1. 祖先组件 App.vue

vue

<template> <div> <h2>主题:{{ theme }}</h2> <button @click="toggleTheme">切换主题</button> <ChildLevel /> </div> </template> <script setup> import { ref, provide } from 'vue' import ChildLevel from './components/ChildLevel.vue' const theme = ref('浅色') // 使用 provide 提供数据:第一个参数是 key(字符串),第二个是值 provide('appTheme', theme) function toggleTheme() { theme.value = theme.value === '浅色' ? '深色' : '浅色' } </script>

2. 中间层组件 ChildLevel.vue

vue

<template> <div> <h3>中间层组件</h3> <GrandChild /> </div> </template> <script setup> import GrandChild from './GrandChild.vue' </script>

3. 孙子组件 GrandChild.vue

vue

<template> <div> <p>孙子组件拿到的主题:{{ theme }}</p> </div> </template> <script setup> import { inject } from 'vue' // 使用 inject 获取祖先提供的 'appTheme' 数据 const theme = inject('appTheme') </script>

案例小结:provide/inject 很适合全局配置、主题、语言这类深层传递的数据。但不要滥用,因为它会让数据流向不那么清晰。


案例六:父调子方法 defineExpose——倒计时控制

场景:子组件内部有一个倒计时功能。父组件想直接调用子组件的“开始”和“重置”方法。

1. 子组件 Countdown.vue

vue

<template> <div> <p>倒计时:{{ count }}</p> </div> </template> <script setup> import { ref } from 'vue' const count = ref(10) // 倒计时数字 let timer = null // 定时器 ID function start() { // 如果已经有定时器在跑,先清除 if (timer) clearInterval(timer) timer = setInterval(() => { if (count.value > 0) { count.value-- } else { clearInterval(timer) // 到 0 停止 } }, 1000) } function reset() { // 清除定时器,重置数字 clearInterval(timer) count.value = 10 } // 把想要暴露给父组件调用的东西列出来 defineExpose({ start, reset }) </script>

2. 父组件 App.vue

vue

<template> <div> <!-- 给子组件加个 ref,这样就能拿到子组件实例 --> <Countdown ref="countdownRef" /> <button @click="startCountdown">开始</button> <button @click="resetCountdown">重置</button> </div> </template> <script setup> import { ref } from 'vue' import Countdown from './components/Countdown.vue' // 这个 ref 的名字要和模板里 ref="countdownRef" 匹配 const countdownRef = ref(null) function startCountdown() { // 通过 .value 拿到子组件实例,然后调用它暴露出来的方法 countdownRef.value.start() } function resetCountdown() { countdownRef.value.reset() } </script>

案例小结:defineExpose+ref可以让父组件直接操作子组件,但不要大面积使用,否则组件耦合太紧。


案例七:全局状态管理 Pinia——购物车

场景:多个页面、多个组件都需要读写购物车数据。这时候用 Pinia 最合适。

1. 安装 Pinia(如果还没装)

bash

npm install pinia

在 main.js 中注册:

javascript

import { createPinia } from 'pinia' const app = createApp(App) app.use(createPinia()) app.mount('#app')

2. 定义 store(stores/cart.js)

javascript

import { defineStore } from 'pinia' import { ref, computed } from 'vue' export const useCartStore = defineStore('cart', () => { // state:购物车商品列表,每项 { id, name, price, count } const items = ref([]) // getter:总价 const totalPrice = computed(() => { return items.value.reduce((sum, item) => sum + item.price * item.count, 0) }) // action:添加商品 function addItem(product) { const exist = items.value.find(item => item.id === product.id) if (exist) { exist.count++ } else { items.value.push({ ...product, count: 1 }) } } // action:清空购物车 function clearCart() { items.value = [] } return { items, totalPrice, addItem, clearCart } })

3. 页面组件中使用

vue

<template> <div> <h2>购物车(总价:{{ cart.totalPrice }} 元)</h2> <ul> <li v-for="item in cart.items" :key="item.id"> {{ item.name }} - {{ item.price }} 元 × {{ item.count }} </li> </ul> <button @click="addSample">添加一个苹果</button> <button @click="cart.clearCart">清空</button> </div> </template> <script setup> import { useCartStore } from '@/stores/cart' const cart = useCartStore() function addSample() { // 添加示例商品 cart.addItem({ id: 1, name: '苹果', price: 5 }) } </script>

案例小结:Pinia 把共享的数据和方法集中管理,任何组件直接引入就能用,是解决复杂通信的终极方案。


练习题

选择题

  1. 父组件向子组件传递数据,应该使用( )
    A. emit
    B. props
    C. provide
    D. v-model

  2. 子组件通知父组件“数据已变化”,应该使用( )
    A. 直接修改 props
    B. defineExpose
    C. emit
    D. provide

  3. v-model 在组件上默认绑定的 prop 名称是( )
    A. value
    B. modelValue
    C. vModel
    D. inputValue

判断题

  1. 子组件可以直接修改父组件传来的 props 的值。( )

  2. provide/inject 适用于任意层级的组件通信,且数据流向容易追踪。( )

编程题

  1. 实现一个“收藏”功能:

    • 父组件显示收藏状态(已收藏/未收藏)

    • 子组件是一个按钮,点击切换收藏状态

    • 要求使用 props 和 emit

  2. 使用 Pinia 实现一个笔记便签:

    • store 里维护一个 notes 数组,每个 note 包含 id 和 text

    • 一个组件负责添加新便签

    • 另一个组件负责展示便签列表


答案

  1. B

  2. C

  3. B

  4. 错误,props 是只读的。

  5. 错误,provide/inject 的数据流向较隐晦,不便于追踪调试。

  6. 实现参考:
    父组件

vue

<template> <div> <p>状态:{{ isCollected ? '已收藏' : '未收藏' }}</p> <CollectButton :collected="isCollected" @toggle="isCollected = !isCollected" /> </div> </template> <script setup> import { ref } from 'vue' import CollectButton from './CollectButton.vue' const isCollected = ref(false) </script>

子组件 CollectButton.vue

vue

<template> <button @click="$emit('toggle')"> {{ collected ? '❤️ 取消收藏' : '🤍 收藏' }} </button> </template> <script setup> defineProps({ collected: Boolean }) defineEmits(['toggle']) </script>
  1. 参考:
    stores/notes.js

javascript

import { defineStore } from 'pinia' import { ref } from 'vue' export const useNotesStore = defineStore('notes', () => { const notes = ref([]) let nextId = 1 function addNote(text) { notes.value.push({ id: nextId++, text }) } return { notes, addNote } })

添加组件

vue

<template> <input v-model="text" placeholder="输入便签" /> <button @click="notes.addNote(text); text=''">添加</button> </template> <script setup> import { ref } from 'vue' import { useNotesStore } from '@/stores/notes' const notes = useNotesStore() const text = ref('') </script>

列表组件

vue

<template> <ul><li v-for="n in notes.notes" :key="n.id">{{ n.text }}</li></ul> </template> <script setup> import { useNotesStore } from '@/stores/notes' const notes = useNotesStore() </script>

最后唠叨两句

组件通信是Vue里最有“工程感”的知识点,初学容易晕,但只要把上面的案例一个个敲一遍,你就能摸清每种方式的脾气。遇到实际问题先想:数据是谁的?谁要听谁的话?再按上面这些套路去选方案,十有八九都能搞定。

有问题评论区直接问,看到必回。下篇见。

http://www.jsqmd.com/news/988484/

相关文章:

  • 2026全年天津律师口碑榜!优质机构评比婚姻策略指导/证据收集/谈判支持 - 资讯快报
  • 模态信息论的逻辑基础
  • 数据的加密与解密(22:30)
  • 2026年6月推荐:工程储罐怎么验 玻璃钢储罐质检流程讲解
  • 工作流智能体_推理型智能体ReAct Agent_Agent平台---AI大模型系统从零开始0008
  • AI领域40多年,真正不变的是什么?
  • 从文化产业到IP授权:五大民营电影公司的“罗曼蒂克消亡史”给出版与内容行业的启示
  • 亚马逊关闭AI榜单,腾讯云ADP 4.0能否破解企业AI落地难题?
  • 2026十万㎡智造基地 + 全国 800 + 门店,方寸之美门窗全维度达标一线门窗品牌权威标准 - 广东科技观察
  • 12601华夏之光永存:黄大年茶思屋榜文126期 第1题 面向一体机内多推理实例混部负载的性能预测和调度算法
  • 114、飞控中的数字信号处理基础
  • 西安市场满意度调查|倾听真实反馈,驱动服务与产品持续升级
  • 2026 济南历下区防水补漏哪家靠谱?正规公司排名及避坑价格指南 - 苏易房屋修缮
  • 数据的加密与解密(22:27)
  • 当香云纱遇见东京:一场跨越千年的东方美学对话
  • 12602华夏之光永存:黄大年茶思屋榜文126期 第2题 进程级抽象到容器级抽象容器原生OS架构解题
  • 外贸开发信总进垃圾箱?6个核心原因排查+自动化预热方案
  • 深度解析代理记账:一篇读懂服务范围与企业价值 - 资讯快报
  • 企业AI提效怎么选?如何用聚合平台解决多模型“信息孤岛”与工具断层?
  • 重排链表避坑思考
  • 2026湖南建康学校招生办电话最新公布|官方招生联系方式与学校全面介绍 - 品牌官
  • Codex App 从0到1完整入门教程
  • 浦东新区唐镇下水道疏通|居顺联家政疏通服务详细介绍 - 居顺联家政疏通
  • 2026年6月天津律师测评!全方位婚姻策略指导/证据收集/谈判支持/诉讼 - 资讯快报
  • 鸿蒙从零掌握核心:幸运数字生成器实战
  • 2026淮安漏水维修攻略|一修匠修缮:厨卫 阳台 外墙 屋顶 地下室|靠谱防水门店 - 绿呼吸检测中心
  • 2026锦州漏水维修攻略|一修匠修缮:厨卫 阳台 外墙 屋顶 地下室|靠谱防水门店 - 绿呼吸检测中心
  • 2026马鞍山漏水维修攻略|一修匠修缮:厨卫 阳台 外墙 屋顶 地下室|靠谱防水门店 - 绿呼吸检测中心
  • MinIO创建存储桶与密钥对赋权操作指南
  • 温变粉厂家选购指南:如何选到靠谱正规的供应商 - 资讯快报