Vue第四篇:组件通信 + DOM 更新 + 过渡动画

Vue 组件化开发中,组件之间传数据(组件通信)是核心需求,DOM 更新时机、元素动效也是前端开发高频场景。

一、自定义事件:子组件向父组件通信的优雅方式

为什么需要自定义事件?
在父组件中,我们通过props向子组件传递数据。但当子组件需要向父组件传递数据时,就需要自定义事件了。

核心作用
自定义事件是子组件给父组件传数据的专属方式(只能子传父)。原理很简单:父组件给子组件绑定一个自定义事件,子组件触发这个事件并传递数据,事件的回调函数写在父组件里,就能轻松拿到子组件的数据。

两种绑定方式对比

方式1:直接在模板中绑定(简洁)

<!-- 父组件 App.vue --><template><div><!-- 通过@绑定自定义事件 --><ChildComponent @child-event="handleChildEvent" /></div></template><script>export default { methods: { handleChildEvent(data){ console.log('收到子组件数据:', data)}}}</script><!-- 子组件 ChildComponent.vue --><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绑定(更灵活)

<!-- 父组件 App.vue --><template><div><!-- 使用ref获取组件引用 --><ChildComponent ref="childRef" /></div></template><script>export default { methods: { handleChildEvent(data){ console.log('收到子组件数据:', data)}}, mounted(){ // 在组件挂载后绑定事件 this.$refs.childRef.$on('child-event', this.handleChildEvent) // 如果只需要触发一次 // this.$refs.childRef.$once('child-event', this.handleChildEvent)}, beforeDestroy(){ // 组件销毁前解绑事件,防止内存泄漏 this.$refs.childRef.$off('child-event')}}</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('事件名', 回调)

重要注意事项

  1. 组件上绑定原生 DOM 事件(比如 click),要加native修饰符,否则会被当成自定义事件:<Student @click.native="handleClick"/>
  2. $refs绑定事件时,回调函数要么写在methods里,要么用箭头函数(如this.$refs.student.$on('jojo', (name) => { console.log(name) })),否则this会指向子组件,而非父组件!

二、全局事件总线:任意组件间的通信桥梁

核心作用
自定义事件只能实现 “子传父”,而全局事件总线能实现任意组件间通信(父传子、子传父、兄弟组件传),是 Vue 中最常用的跨组件通信方式,本质就是一个所有组件都能访问的 “全局对象”。

核心条件

全局事件总线必须满足 2 个要求:

  1. 所有组件都能访问到这个对象(挂载到 Vue 原型上);
  2. 这个对象要有$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(){ // 触发全局事件demo,把学生名传递过去 this.$bus.$emit('demo', this.name);}}}</script>

使用步骤

  1. 安装总线:在main.js的 Vue 实例中,beforeCreate钩子里写Vue.prototype.$bus = this
  2. 接收数据:A 组件想收数据 → A 组件mounted钩子中执行this.$bus.$on('事件名', 回调函数)
  3. 发送数据:B 组件想发数据 → B 组件中执行this.$bus.$emit('事件名', 要传的数据)
  4. 解绑事件:A 组件beforeDestroy钩子中执行this.$bus.$off('事件名')(必做,避免内存泄漏)

三、消息订阅与发布:另一种全局通信方案

核心作用
和全局事件总线功能一致,也是任意组件间通信,但需要借助第三方库pubsub-js实现。日常开发中用得少,因为全局事件总线已能满足需求,无需额外安装依赖。

为什么选择pubsub?
虽然Vue有事件总线,但在某些场景下,你可能需要:

  • 更精细的控制(取消特定订阅)
  • 跨框架通信(与非Vue组件通信)
  • 使用已有的第三方库生态

完整案例(Student→School 通信)

1. 安装依赖

npm i pubsub-js 

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> // 引入pubsub-js库 import pubsub from 'pubsub-js'export default { name: 'Student', data(){return{ name: 'JOJO', sex: '男'}}, methods: {sendStudentName(){ // 发布消息:消息名demo,要传递的数据 pubsub.publish('demo', this.name);}}}</script>

使用步骤

  1. 安装:npm i pubsub-js;
  2. 引入:import pubsub from ‘pubsub-js’;
  3. 订阅消息(收数据):this.pubId = pubsub.subscribe(‘消息名’, 回调函数);
  4. 发布消息(发数据):pubsub.publish(‘消息名’, 要传的数据);
  5. 取消订阅:pubsub.unsubscribe(this.pubId)(组件销毁前执行)。

事件总线 vs pubsub

特性Vue事件总线pubsub-js
依赖Vue实例独立库
语法this.$bus.$emit / $onpublish / subscribe
取消订阅$offunsubscribe
适用范围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)

/* 自定义动画样式,name是hello,所以样式前缀为hello- */ /* 进入动画:起点 → 过程 → 终点 */ .hello-enter { opacity: 0; /* 进入起点:透明 */ transform: translateX(100px); /* 进入起点:右移100px */ } .hello-enter-active { transition: all 0.5s ease; /* 进入过程:0.5秒过渡 */ } .hello-enter-to { opacity: 1; /* 进入终点:不透明 */ transform: translateX(0); /* 进入终点:回到原位 */ } /* 离开动画:起点 → 过程 → 终点 */ .hello-leave { opacity: 1; /* 离开起点:不透明 */ transform: translateX(0); /* 离开起点:原位 */ } .hello-leave-active { transition: all 0.5s ease; /* 离开过程:0.5秒过渡 */ } .hello-leave-to { opacity: 0; /* 离开终点:透明 */ transform: translateX(-100px); /* 离开终点:左移100px */ }

2. 用<transition>包裹元素

<template><div><button @click="isShow = !isShow">显示/隐藏</button><!-- transition包裹要加动效的元素,name对应CSS样式前缀 --><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包裹多元素,必须加key --><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" />

2. 谨慎使用ref直接操作

// 尽量避免 this.$refs.child.doSomething() // 优先通过事件通信 this.$refs.child.$emit('do-something')

3. 事件总线要记得清理

// 必须的!防止内存泄漏 beforeDestroy(){ this.$bus.$off('event-name', this.handler)}

4. 合理使用$nextTick

// 只在需要操作更新后的DOM时使用 this.data = newValue this.$nextTick(()=>{ // 这里DOM已经更新 })

总结

核心要点回顾

  • 自定义事件:子组件向父组件通信的标准方式
  • 全局事件总线:任意组件间通信的轻量级方案
  • 消息订阅发布:跨框架通信的备选方案
  • $nextTick:处理DOM异步更新的关键方法过渡动画:使用Vue内置组件实现平滑的UI效果

Read more

告别传输焦虑!LocalSend:一款免费开源的跨平台局域网秒传神器

日常工作生活中,你是否总被文件传输的问题困扰?微信传文件限200MB、QQ传大文件进度条卡98%、蓝牙传照片慢到“怀疑人生”、U盘随手一放就弄丢……这些痛点,让原本简单的“分享”变成了耗时耗力的麻烦事。 直到发现 LocalSend——这款被称作“去中心化隔空投送”的免费开源工具,彻底解决了局域网内跨设备传输的所有难题。它无需互联网、不经过第三方服务器,却能实现手机、电脑、电视甚至树莓派之间的高速文件互传,今天就带大家全面了解这款工具的魅力。 一、认识LocalSend:不止是“局域网传输工具” LocalSend是一款免费、开源、跨全平台的本地文件共享应用,核心定位是“去中心化的Airdrop(隔空投送)”。与传统工具不同,它不依赖云端或公网,仅通过局域网(WiFi/热点)就能让设备间直接发现、连接并传输文件,从根源上保障了数据隐私与传输效率。 从支持范围来看,它几乎覆盖了所有主流设备: * 桌面端:Windows、macOS、Linux(含Ubuntu、CentOS等) * 移动端:

By Ne0inhk

git详细使用教程

文章目录 * 一、 git介绍与安装 * 1、git介绍 * 2、git的安装 * 3、git使用前的说明 * 二、git的基础使用 * 1、走进git之前 * 2、git基础使用 * 1、`git init` 项目初始化(`init`)成仓库(`repository`) * 2、`git add` 管理文件 * 3、`git commit` 把文件提交到仓库,命令: * 三、git 的高级使用 * 1、git的高级使用1 * 1、`git reset --hard 版本号` 版本回滚 * 2、`git reflog` 查看所有的提交记录 * 2、git 的高级使用2 * 1、

By Ne0inhk
github copilot学生认证教程,免费使用两年Copilot Pro!!(避免踩坑版)

github copilot学生认证教程,免费使用两年Copilot Pro!!(避免踩坑版)

先放结果,本人是先后申请了三次: 1、第一次直接用的学生证,打开对着电脑摄像头直接拍了一张,失败了,如下,理由是没有开启双重认证!!,并且学生证内页没有学校名称!! 2、第二次开了双重认证之后我又重新提交了一次,这次使用的是学信网上的中英文对照截图,又失败了,理由如下: 简单来说就是,(1)开了代理;(2)定位不在学校附近,也就是与主页信息处的Location不相符(这个后面会讲!);(3)个人信息不完整 3、在前面所有错误修改完善之后,我又查看了大量的相关帖子和教程,最终打造出一个完美的申请流程,终于出现了这个,而且是秒通过!!! --------------------------------------------------------------------------------------------------------------------------------- 本文所有步骤均为实操,安全有保障,帖子随意看,对您有用的话还希望给个三连,祝好运!! 下面开始手把手教程,保证详细,仅此一篇足以!!! 一、申请前提 1、GitHub账号一个,ht

By Ne0inhk
告别996:GitHub Copilot将我的开发效率提升300%的实战记录

告别996:GitHub Copilot将我的开发效率提升300%的实战记录

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕AI这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * 告别996:GitHub Copilot将我的开发效率提升300%的实战记录 * 引言:从疲惫到高效 * 什么是GitHub Copilot?🤖 * 效率提升300%的核心场景 * 1. 快速生成样板代码 * 2. 自动编写单元测试 * 3. 智能调试与注释 * 集成Copilot到工作流 * 步骤1:设置合理的期望 * 步骤2:结合IDE使用 * 步骤3:代码审查与调整 * 高级用法:超越代码生成 * 数据库查询优化 * API接口设计 * 正则表达式助手 * 数据支撑:效率提升分析 * 避坑指南:常见问题与解决 * 1. 可能生成过时或不安全代码

By Ne0inhk