跳到主要内容Vue第四篇:组件通信 + DOM 更新 + 过渡动画 | 极客日志Python
Vue第四篇:组件通信 + DOM 更新 + 过渡动画
Vue 组件化开发中,组件之间传数据(组件通信)是核心需求,DOM 更新时机、元素动效也是前端开发高频场景。 一、自定义事件:子组件向父组件通信的优雅方式 **为什么需要自定义事件?** 在父组件中,我们通过props向子组件传递数据。但当子组件需要向父组件传递数据时,就需要自定义事件了。 **核心作用** 自定义事件是**子组件给父组件传数据**的专属方式(只能子传父)。原理很简单:父组件给子组…
山野诗人39K 浏览 Vue 组件化开发中,组件之间传数据(组件通信)是核心需求,DOM 更新时机、元素动效也是前端开发高频场景。
一、自定义事件:子组件向父组件通信的优雅方式
为什么需要自定义事件?
在父组件中,我们通过props向子组件传递数据。但当子组件需要向父组件传递数据时,就需要自定义事件了。
核心作用
自定义事件是子组件给父组件传数据的专属方式(只能子传父)。原理很简单:父组件给子组件绑定一个自定义事件,子组件触发这个事件并传递数据,事件的回调函数写在父组件里,就能轻松拿到子组件的数据。
两种绑定方式对比
方式1:直接在模板中绑定(简洁)
<template><div><ChildComponent @child-event="handleChildEvent" /></div></template><script>export default { methods: { handleChildEvent(data){ console.log('收到子组件数据:', data)}}}</script><template><button @click="sendData">发送数据给父组件</button></template><script>export default { methods: {sendData(){ this.$emit('child-event', { message: 'Hello from child!', timestamp: new Date()})}}}</script>
方式2:使用ref绑定(更灵活)
<template><div><ChildComponent ref="childRef" /></div></template><script>export default { methods: { handleChildEvent(data){ console.log('收到子组件数据:', data)}}, mounted(){ </script>
自定义事件完整生命周期
// 子组件内部 export default { methods: { // 触发事件(发送数据) sendMessage(){ this.$emit('message', 'Hello!')}, // 解绑单个事件 unbindSingle(){ this.$off('message')}, // 解绑多个事件 unbindMultiple(){ this.$off(['message', 'other-event'])}, // 解绑所有事件 unbindAll(){ this.$off()}}}
核心总结
| 操作 | 具体写法 |
|---|
| 绑定自定义事件 | 方式 1:<Demo @事件名="回调函数"/>(@是 v-on 简写) |
方式 2:this.$refs.子组件.$on('事件名', 回调) | |
| 触发自定义事件 | this.$emit('事件名', 要传递的数据) |
| 解绑自定义事件 | this.$off('事件名')(单个)/ this.$off([事件1,事件2])(多个)/ this.$off()(全部) |
| 只触发一次 | 方式 1:<Demo @事件名.once="回调"/> |
方式 2:this.$refs.子组件.$once('事件名', 回调) | |
- 组件上绑定原生 DOM 事件(比如 click),要加native修饰符,否则会被当成自定义事件:
<Student @click.native="handleClick"/>;
- 用
$refs绑定事件时,回调函数要么写在methods里,要么用箭头函数(如this.$refs.student.$on('jojo', (name) => { console.log(name) })),否则this会指向子组件,而非父组件!
二、全局事件总线:任意组件间的通信桥梁
核心作用
自定义事件只能实现 '子传父',而全局事件总线能实现任意组件间通信(父传子、子传父、兄弟组件传),是 Vue 中最常用的跨组件通信方式,本质就是一个所有组件都能访问的 '全局对象'。
- 所有组件都能访问到这个对象(挂载到 Vue 原型上);
- 这个对象要有
$on(绑定事件)、$emit(触发事件)、$off(解绑事件)方法(Vue 实例 / 组件实例自带这些方法)。
1. 安装全局事件总线(src/main.js)
import Vue from 'vue'import App from './App.vue' Vue.config.productionTip =false // 关闭生产提示 new Vue({ el: '#app', render: h => h(App), // 关键:在beforeCreate钩子中安装全局事件总线 beforeCreate(){ // 把Vue实例(this)挂载到Vue原型上,命名为$bus // 所有组件都能通过this.$bus访问这个全局对象 Vue.prototype.$bus= this
2. 接收数据的组件(src/components/School.vue)
<template><div class="school"><h2>学校名称:{{ name }}</h2><h2>学校地址:{{ address }}</h2></div></template><script>export default { name: 'School', data(){return{ name: '尚硅谷', address: '北京'}}, methods: { // 接收数据的回调函数 demo(data){ console.log('我是School组件,收到了Student组件的数据:', data);}}, mounted(){ // 绑定全局事件:事件名demo,回调函数demo this.$bus.$on('demo', this.demo);}, beforeDestroy(){ // 组件销毁前解绑事件,避免内存泄漏 this.$bus.$off('demo');}}</script>
3. 发送数据的组件(src/components/Student.vue)
<template><div class="student"><h2>学生姓名:{{ name }}</h2><h2>学生性别:{{ sex }}</h2><button @click="sendStudentName">把学生名给School组件</button></div></template><script>export default { name: 'Student', data(){return{ name: '张三', sex: '男'}}, methods: {sendStudentName(){ </script>
- 安装总线:在
main.js的 Vue 实例中,beforeCreate钩子里写Vue.prototype.$bus = this;
- 接收数据:A 组件想收数据 → A 组件
mounted钩子中执行this.$bus.$on('事件名', 回调函数);
- 发送数据:B 组件想发数据 → B 组件中执行
this.$bus.$emit('事件名', 要传的数据);
- 解绑事件:A 组件beforeDestroy钩子中执行
this.$bus.$off('事件名')(必做,避免内存泄漏)
三、消息订阅与发布:另一种全局通信方案
核心作用
和全局事件总线功能一致,也是任意组件间通信,但需要借助第三方库pubsub-js实现。日常开发中用得少,因为全局事件总线已能满足需求,无需额外安装依赖。
为什么选择pubsub?
虽然Vue有事件总线,但在某些场景下,你可能需要:
- 更精细的控制(取消特定订阅)
- 跨框架通信(与非Vue组件通信)
- 使用已有的第三方库生态
完整案例(Student→School 通信)
1. 安装依赖
2. 接收数据的组件(src/components/School.vue)
<template><div class="school"><h2>学校名称:{{ name }}</h2><h2>学校地址:{{ address }}</h2></div></template><script> // 引入pubsub-js库 import pubsub from 'pubsub-js'export default { name: 'School', data(){return{ name: '尚硅谷', address: '北京'}}, methods: { // 回调函数:第一个参数是消息名,第二个才是真正的数据 demo(msgName, data){ console.log('我是School组件,收到了数据:', data);}}, mounted(){ // 订阅消息:消息名demo,回调demo,返回订阅ID(用于取消订阅) this.pubId = pubsub.subscribe('demo', this.demo);}, beforeDestroy(){ // 取消订阅(必须传订阅ID) pubsub.unsubscribe(this.pubId);}}</script>
3. 发送数据的组件(src/components/Student.vue)
<template><div class="student"><h2>学生姓名:{{ name }}</h2><h2>学生性别:{{ sex }}</h2><button @click="sendStudentName">把学生名给School组件</button></div></template><script> </script>
- 安装:npm i pubsub-js;
- 引入:import pubsub from 'pubsub-js';
- 订阅消息(收数据):this.pubId = pubsub.subscribe('消息名', 回调函数);
- 发布消息(发数据):pubsub.publish('消息名', 要传的数据);
- 取消订阅:pubsub.unsubscribe(this.pubId)(组件销毁前执行)。
| 特性 | Vue事件总线 | pubsub-js |
|---|
| 依赖 | Vue实例 | 独立库 |
| 语法 | this.$bus.$emit / $on | publish / subscribe |
| 取消订阅 | $off | unsubscribe |
| 适用范围 | Vue组件间 | 任意JS环境 |
| 学习成本 | 低(Vue自带) | 低(简单API) |
四、$nextTick:等待DOM更新的神器
为什么需要$nextTick?
Vue的数据更新是异步的。当你修改数据后,DOM并不会立即更新,而是进入一个队列,等待下一个"tick"(时机)【所有数据更新完】统一更新。
$nextTick 能让代码 '等 DOM 更新完成后再执行',避免拿到旧的 DOM 节点。
场景
比如修改isShow让输入框显示,想立刻让输入框聚焦:
// 错误写法:DOM还没更新,找不到输入框,会报错 this.isShow =true; this.$refs.input.focus(); // 正确写法:用$nextTick等DOM更新完再聚焦 this.isShow =true; this.$nextTick(()=>{ this.$refs.input.focus(); // 成功聚焦! });
| 项 | 说明 |
|---|
| 语法 | 方式 1:this.$nextTick(回调函数) |
方式 2:await this.$nextTick()(返回 Promise) | |
| 作用 | 下次 DOM 更新循环结束后执行回调函数 |
| 使用场景 | 修改数据后,需要操作更新后的 DOM(比如聚焦输入框、获取新 DOM 的高度 / 宽度) |
五、过渡与动画:让页面动起来
核心作用
Vue 封装的<transition>/<transition-group>组件,能给 '插入 / 更新 / 移除 DOM 元素' 的过程添加动效,不用自己写复杂的 CSS 动画逻辑。
1. 编写动画样式(CSS)
.hello-enter { opacity: 0; transform: translateX(100px); } .hello-enter-active { transition: all 0.5s ease; } .hello-enter-to { opacity: 1; transform: translateX(0); } .hello-leave { opacity: 1; transform: translateX(0); } .hello-leave-active { transition: all 0.5s ease; } .hello-leave-to { opacity: 0; transform: translateX(-100px); }
2. 用<transition>包裹元素
<template><div><button @click="isShow = !isShow">显示/隐藏</button><transition name="hello"><h1 v-show="isShow">你好啊!</h1></transition></div></template><script>export default {data(){return{ isShow: true </script>
3. 多元素动效(<transition-group>)
如果有多个元素需要加动效,必须用<transition-group>,且每个元素要指定唯一key:
<template><div><button @click="addItem">添加元素</button><transition-group name="hello"><div v-for="(item, index) in list" :key="index" v-show="item.show">{{ item.text }}</div></transition-group></div></template><script>export default {data(){return{ list: [{ text: '第一个元素', show: true}, { text: '第二个元素', show: true}]}}, methods: {addItem(){ this.list.push({ text: `新元素${Date.now()}`, show: true});}}}</script>
| 项 | 写法 / 说明 |
|---|
| 单个元素 | 用<transition name="自定义名">包裹,CSS 样式前缀对应 name 值 |
| 多个元素 | 用<transition-group>包裹,每个元素必须加key |
| 动画样式 | 进入:v-enter /v-enter-active/v-enter-to |
离开:v-leave /v-leave-active/v-leave-to(v 会被 name 替换) | |
六、通信方案选择指南
| 场景 | 推荐方案 | 说明 |
|---|
| 父子组件通信 | props + 自定义事件 | Vue推荐的标准方式 |
| 父调用子方法 | ref | 直接访问子组件实例 |
| 兄弟组件通信 | 全局事件总线 | 通过共同父组件中转或使用事件总线 |
| 跨多层组件 | 全局事件总线 / Vuex | 避免props逐层传递 |
| 非Vue组件间 | pubsub | 与其他JS库/框架通信 |
| 简单状态共享 | 事件总线 | 小型项目快速实现 |
| 复杂状态管理 | Vuex | 中大型项目,需要状态追踪 |
最佳实践建议
1. 优先使用props和自定义事件
<Child :data="parentData" @update="handleUpdate" />
// 尽量避免 this.$refs.child.doSomething() // 优先通过事件通信 this.$refs.child.$emit('do-something')
// 必须的!防止内存泄漏 beforeDestroy(){ this.$bus.$off('event-name', this.handler)}
// 只在需要操作更新后的DOM时使用 this.data = newValue this.$nextTick(()=>{ // 这里DOM已经更新 })
总结
- 自定义事件:子组件向父组件通信的标准方式
- 全局事件总线:任意组件间通信的轻量级方案
- 消息订阅发布:跨框架通信的备选方案
- $nextTick:处理DOM异步更新的关键方法过渡动画:使用Vue内置组件实现平滑的UI效果
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown 转 HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
- HTML 转 Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online