✅ 今日学习核心:class/style 样式绑定 + ref 获取 DOM / 组件 + 组件通信全套方案 + 事件总线 + Vue 异步更新原理 + 解决方案 ,全是高频重点,企业开发天天用,知识点循序渐进,全部配极简案例,学完直接上手写代码!✅ 前置说明:所有代码都基于 Vue2 的标准语法,延续第一天的 Vue 实例写法,学完今天内容,Vue2 的核心基础语法就全部掌握了!
一、class 与 style 动态样式绑定(第一天v-bind的核心拓展,高频必考)
我们第一天学了 v-bind(:) 是属性绑定指令 ,class(类名)和 style(行内样式)是 HTML 标签的属性,所以动态样式本质就是 v-bind 的专属应用场景,开发中 100% 用到,必须吃透!
✅ 核心语法
绑定样式时,指令的值支持两种写法:对象语法 、数组语法 ,均无需写{``{ }},直接写表达式 / 变量。
1. 动态绑定 class 样式(最常用)
✔️ 语法 1:对象语法(推荐,控制「单个 / 多个类名的显示 / 隐藏」)
vue
<div :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>
- 规则:布尔值为
true→ 该类名生效;布尔值为false→ 该类名不生效 - 核心场景:开关类样式(比如选中高亮、按钮禁用样式、是否显示边框)
- 支持:可以和原生 class 共存,原生 class 写固定样式,绑定的 class 写动态样式
✔️ 示例代码(class 对象语法)
html
预览
<div id="app">
<!-- 原生class + 动态class共存 -->
<div class="base" :class="{ active: isActive, danger: isRed }">我是测试样式</div>
<button @click="isActive = !isActive">切换高亮</button>
</div>
<style>
.base { width: 200px; height: 50px; line-height: 50px; }
.active { background: skyblue; color: #fff; }
.danger { border: 2px solid red; }
</style>
<script>
new Vue({ el: '#app', data: { isActive: true, isRed: false } })
</script>
✔️ 语法 2:数组语法(控制「类名的列表切换」)
vue
<div :class="[类名1, 类名2, 变量名]"></div>
- 规则:数组里写什么类名,就渲染什么类名;数组里可以是字符串固定类名 ,也可以是data 里的变量
- 核心场景:批量绑定多个类名、根据变量动态切换不同类名
✔️ 示例代码(class 数组语法)
vue
<div id="app">
<div :class="[baseClass, activeClass]">数组语法样式</div>
</div>
<script>
new Vue({ el: '#app', data: { baseClass: 'base', activeClass: 'active' } })
</script>
2. 动态绑定 style 行内样式
✔️ 语法:对象语法(唯一常用写法)
vue
<div :style="{ css属性名: 属性值, css属性名: 属性值 }"></div>
- 注意 1:css 属性名是驼峰命名 (比如
fontSize代替font-size,backgroundColor代替background-color) - 注意 2:属性值如果是固定值 加引号,是变量 / 数字直接写,无需引号
- 核心场景:根据后台返回数据动态设置样式(比如动态设置宽高、颜色、字体大小)
✔️ 示例代码
vue
<div id="app">
<div :style="{ width: '200px', height: heightVal, backgroundColor: bgColor }">动态行内样式</div>
</div>
<script>
new Vue({ el: '#app', data: { heightVal: '60px', bgColor: 'pink' } })
</script>
二、ref 关键字 - 获取 DOM 元素 & 获取 子组件实例(核心重点,必会!)
✅ 为什么需要 ref?
Vue 的核心思想是「数据驱动视图 」,不推荐我们手动操作 DOM,但是在某些特殊场景下(比如获取输入框的值、操作元素滚动条、调用子组件的方法),必须要拿到 DOM 元素 / 子组件,此时 Vue 提供了 ref + $refs 语法,这是 Vue 官方推荐的「获取 DOM / 组件」的方式,取代原生的 document.getElementById。
✅ ref 核心语法(三步走,固定规则,终身受用)
1. 绑定 ref 属性
给需要获取的 DOM 标签 或 子组件标签 上,添加 ref="自定义名称" 属性
vue
<!-- 给DOM元素加ref -->
<input type="text" ref="myInput">
<!-- 给子组件加ref -->
<Son ref="sonCom"></Son>
2. 获取 ref 绑定的元素 / 组件
在 Vue 的 methods方法 / 生命周期中,通过 this.$refs.自定义名称 获取
js
// 获取DOM元素
this.$refs.myInput
// 获取子组件实例
this.$refs.sonCom
3. 使用获取到的内容
- 获取 DOM 后:可以调用原生 DOM 的属性和方法(比如
.value获取输入框值、.focus()让输入框聚焦) - 获取子组件后:可以直接访问子组件的 data 数据 、直接调用子组件的 methods 方法
✅ 两个核心使用场景(分开展示,清晰易懂)
✔️ 场景 1:ref 获取 DOM 元素(最常用)
html
预览
<div id="app">
<input type="text" ref="inp" value="我是输入框">
<button @click="getDom">点击获取DOM</button>
</div>
<script>
new Vue({
el: '#app',
methods: {
getDom() {
// 获取DOM元素
const inpDom = this.$refs.inp
// 调用DOM原生属性/方法
console.log('输入框的值:', inpDom.value) // 输出:我是输入框
inpDom.focus() // 输入框自动聚焦
inpDom.style.color = 'red' // 修改输入框文字颜色
}
}
})
</script>
✔️ 场景 2:ref 获取 子组件实例(组件通信的补充方案)
前置:这里先知道用法即可,后面组件通信会详细讲,核心是父组件可以通过 ref 直接操作子组件的所有内容
vue
<div id="app">
<Son ref="sonRef"></Son>
<button @click="getSon">获取子组件</button>
</div>
<script>
// 定义子组件
Vue.component('Son', {
data() {
return { sonMsg: '我是子组件的data数据' }
},
methods: {
sonFn() { alert('我是子组件的方法') }
},
template: '<div>我是子组件</div>'
})
new Vue({
el: '#app',
methods: {
getSon() {
const son = this.$refs.sonRef
console.log(son.sonMsg) // 访问子组件data → 输出:我是子组件的data数据
son.sonFn() // 调用子组件方法 → 弹出提示框
}
}
})
</script>
✅ ref 重要注意点(避坑必备)
- ref 的值是唯一的,不要给多个元素 / 组件写相同的 ref 名称;
this.$refs只能在 Vue 实例挂载完成后 使用(比如 methods 方法、mounted 生命周期),在 created 中使用会获取不到(因为 DOM 还没渲染);- 如果给
v-for循环的元素加 ref,this.$refs.名称获取到的是一个数组,里面是所有循环的 DOM / 组件。
三、Vue2 组件通信 全套方案(重中之重,Vue2 核心难点 + 面试必考,今天吃透 80%)
✅ 前置必懂:什么是组件通信?
- 组件通信:就是 组件和组件之间的数据传递、方法调用。
- 为什么需要通信:Vue 是组件化开发 ,页面被拆分成多个独立组件(父、子、兄弟),每个组件的
data是独立的、私有化的(组件 A 的 data,组件 B 不能直接访问),但开发中组件之间必然需要共享数据,所以必须通过指定的通信方式实现。 - 组件关系分类:父子组件(最核心)、兄弟组件、隔代组件(祖孙)、任意组件
✅ 核心原则(必须记住)
Vue2 组件通信遵循 「单向数据流」 :父组件可以给子组件传数据 / 方法,子组件不允许直接修改父组件的数据,只能通过调用父组件的方法来间接修改。目的是:让数据流向清晰,方便维护,避免多个组件乱改数据导致 bug。
✅ 通信方案 1:父组件 → 子组件 传值(最常用,props,核心必背)
适用场景 :父组件把自己的 data数据 / 方法 / 常量,传递给子组件使用,唯一正向传值方式。
✔️ 实现步骤(固定 3 步,无脑套用)
- 父组件中:在子组件标签上 写
自定义属性名="要传递的数据" - 子组件中:通过
props数组 / 对象 接收 父组件传递的属性 - 子组件中:直接使用(像用自己的 data 数据一样,{{属性名}} / 直接调用)
✔️ 示例代码(父传子 完整案例)
vue
<div id="app">
<!-- 1. 父组件:子组件标签绑定自定义属性,传数据 -->
<Son :msg="parentMsg" :age="18" :user="parentUser"></Son>
</div>
<script>
Vue.component('Son', {
// 2. 子组件:props接收父组件的传值(数组写法,简单场景用)
props: ['msg', 'age', 'user'],
template: `
<div>
<p>父组件传的文字:{{ msg }}</p>
<p>父组件传的年龄:{{ age }}</p>
<p>父组件传的对象:{{ user.name }}</p>
</div>
`
})
new Vue({
el: '#app',
data: {
parentMsg: '我是父组件的数据',
parentUser: { name: '张三', sex: '男' }
}
})
</script>
✔️ props 进阶:对象写法(推荐,带数据校验,开发规范)
可以限制接收的数据类型、是否必填、默认值,报错时控制台会有提示,方便排查问题
js
props: {
msg: String, // 限制msg必须是字符串类型
age: {
type: Number, // 类型
required: true, // 是否必填
default: 20 // 默认值(非必填时生效)
},
user: {
type: Object,
default() { return {} } // 对象/数组的默认值,必须写函数返回
}
}
✔️ 父传子 重要注意点
- props 中的数据是只读的 ,子组件绝对不能直接修改 (比如
this.msg = '新值'会报错),违反单向数据流; - props 可以接收任意类型数据:字符串、数字、布尔值、数组、对象、甚至父组件的方法。
✅ 通信方案 2:子组件 → 父组件 传值(最常用,$emit,核心必背)
适用场景 :子组件想把自己的数据传递给父组件,或者子组件想触发父组件的方法,唯一反向传值方式。
核心原理:Vue 规定子不能直接改父的数据,所以子组件通过「发布事件 」的方式通知父组件,父组件「订阅事件」后自己修改数据,本质是「子调用父的方法,把数据当参数传过去」。
✔️ 实现步骤(固定 3 步,无脑套用)
- 父组件中:在子组件标签上 绑定自定义事件
@自定义事件名="父组件的方法" - 子组件中:在需要传值的地方,调用
this.$emit('自定义事件名', 要传递的参数)发布事件 - 父组件中:在绑定的方法里,通过形参接收子组件传递的参数
✔️ 示例代码(子传父 完整案例)
vue
<div id="app">
<p>父组件接收的子组件数据:{{ sonData }}</p>
<!-- 1. 父组件:绑定自定义事件,关联自己的方法 -->
<Son @sendData="getParentData"></Son>
</div>
<script>
Vue.component('Son', {
data() { return { sonMsg: '我是子组件要传递的数据' } },
template: `
<div>
<button @click="send">点击给父组件传值</button>
</div>
`,
methods: {
send() {
// 2. 子组件:发布事件,传递参数
this.$emit('sendData', this.sonMsg)
}
}
})
new Vue({
el: '#app',
data: { sonData: '' },
methods: {
// 3. 父组件:方法接收参数
getParentData(val) {
this.sonData = val
}
}
})
</script>
✅ 通信方案 3:ref 实现 父子组件通信(补充方案,简单粗暴)
就是我们上面学的 ref 用法,本质是父组件主动获取子组件的一切内容,也算通信的一种,开发中常用,适合简单场景。
✔️ 核心能力
- 父 → 子:父组件通过
this.$refs.子组件ref名.子组件data直接访问子组件数据 - 父 → 子:父组件通过
this.$refs.子组件ref名.子组件方法()直接调用子组件方法 - 子 → 父:子组件通过
this.$parent.父组件data/this.$parent.父组件方法()访问父组件内容(不推荐,耦合度高)
✅ 通信方案 4:事件总线(EventBus)- 万能通信方案(核心必背,今天重点)
✔️ 适用场景
解决 兄弟组件、隔代组件、任意组件 之间的通信问题,是 Vue2 中最灵活、最常用的万能通信方案 ,弥补了props/$emit只能父子通信的缺陷,企业开发高频使用!
✔️ 核心原理
事件总线的本质:创建一个全局的 Vue 空实例 ,这个实例作为「中转站」,所有需要通信的组件,都通过这个实例来 发布事件 (emit)** 和 **订阅事件(on),从而实现数据传递。
- 发布事件:
总线实例.$emit('事件名', 传递的数据)→ 发送数据 - 订阅事件:
总线实例.$on('事件名', (参数) => { 接收数据 })→ 接收数据
✔️ 实现步骤(固定 4 步,全局通用,所有组件都能这么用)
第一步:创建全局的事件总线(写在 Vue 实例创建之前,全局只写一次)
js
// 创建一个空的Vue实例,作为事件总线
Vue.prototype.$bus = new Vue()
语法说明:
Vue.prototype.$bus是给 Vue 的原型上挂载了一个全局属性$bus,所有 Vue 组件都能通过this.$bus访问到这个总线实例。
第二步:发送数据的组件(发布事件)
在需要传值的组件中,调用 this.$bus.$emit('自定义事件名', 要传递的数据)
js
methods: {
sendMsg() {
// 发布事件,传递数据
this.$bus.$emit('share', '我是任意组件传递的数据')
}
}
第三步:接收数据的组件(订阅事件)
在需要接收数据的组件中,在 created 生命周期中调用 this.$bus.$on('自定义事件名', 回调函数),通过回调函数的形参接收数据
js
created() {
// 订阅事件,接收数据,事件名必须和发布的一致
this.$bus.$on('share', (val) => {
console.log('接收的数据:', val)
this.msg = val
})
}
第四步:解绑事件(推荐,优化性能,必加)
在接收数据的组件的 beforeDestroy 生命周期中,调用 this.$bus.$off('事件名') 解绑事件,避免组件销毁后事件残留导致重复触发
js
beforeDestroy() {
// 解绑指定事件
this.$bus.$off('share')
}
✔️ 事件总线 完整示例(兄弟组件通信)
vue
<div id="app">
<Brother1></Brother1>
<Brother2></Brother2>
</div>
<script>
// 1. 创建全局事件总线(重中之重,必须写在组件定义之前)
Vue.prototype.$bus = new Vue()
// 兄弟组件1:发送数据
Vue.component('Brother1', {
template: '<button @click="send">我是兄组件,点击传值</button>',
methods: {
send() { this.$bus.$emit('sendData', '兄组件的数据') }
}
})
// 兄弟组件2:接收数据
Vue.component('Brother2', {
data() { return { msg: '' } },
template: '<p>我是弟组件,接收的值:{{ msg }}</p>',
created() {
// 订阅事件
this.$bus.$on('sendData', (val) => { this.msg = val })
},
beforeDestroy() {
// 解绑事件
this.$bus.$off('sendData')
}
})
new Vue({ el: '#app' })
</script>
四、Vue2 异步更新 DOM 原理 + 解决方案(核心难点,面试必考,开发避坑必备)
✅ 先看一个现象:为什么数据变了,页面却没立刻更新?
先写一段代码,你会发现一个奇怪的问题:
vue
<div id="app">
<p ref="myP">{{ num }}</p>
<button @click="add">点击+1</button>
</div>
<script>
new Vue({
el: '#app',
data: { num: 0 },
methods: {
add() {
this.num++
// 打印DOM中的内容
console.log(this.$refs.myP.innerText) // 输出:0,不是1!
}
}
})
</script>
问题 :明明执行了 this.num++,数据已经变成 1 了,但是打印 DOM 的内容还是 0,页面要等一会才显示 1。答案 :因为 Vue2 的 DOM 更新是 异步的!
✅ 核心原理:Vue2 为什么要异步更新 DOM?
- Vue 在检测到
data中的数据发生变化时,不会立刻去更新 DOM ,而是会把「DOM 更新的任务」加入到一个 异步更新队列 中; - 当同一轮事件循环中,有多次数据修改 时,Vue 会对队列进行「去重合并」,只执行一次 DOM 更新;
- 这样做的目的:减少 DOM 的操作次数,提升页面渲染性能(DOM 操作是浏览器最耗性能的操作,能少则少)。
一句话总结 :数据更新是同步的,DOM 更新是异步的 → 数据变了,DOM 会 "偷懒" 等一等,攒够了一起更。
✅ 解决方案:如何在数据更新后,立刻拿到最新的 DOM?
Vue 官方提供了 2 个方案 ,都是开发中常用的,无脑记,无脑用,优先级:方案 1 > 方案 2
✔️ 方案 1:使用 Vue.nextTick(回调函数) 【推荐,最常用】
- 语法:
this.$nextTick(() => { 这里写操作DOM的代码 }) - 作用:等待当前数据更新完成,DOM 渲染完毕后,再执行回调函数里的代码
- 特点:全局方法,所有 Vue 实例都能调用,精准匹配异步更新时机,无副作用。
✔️ 方案 2:使用 setTimeout(回调函数, 0)
- 语法:
setTimeout(() => { 操作DOM的代码 }, 0) - 作用:利用 JS 的事件循环机制,把 DOM 操作放到下一轮事件循环执行,此时 DOM 已经更新完毕。
- 特点:简单粗暴,但是优先级低于
$nextTick,不推荐在复杂场景使用。
✔️ 解决上面问题的完整代码
vue
<div id="app">
<p ref="myP">{{ num }}</p>
<button @click="add">点击+1</button>
</div>
<script>
new Vue({
el: '#app',
data: { num: 0 },
methods: {
add() {
this.num++
// ✅ 方案1:使用$nextTick,立刻拿到最新DOM
this.$nextTick(() => {
console.log(this.$refs.myP.innerText) // 输出:1 ✔️
})
}
}
})
</script>
✅ $nextTick 核心使用场景(开发中必用)
- 修改数据后,立刻获取更新后的 DOM 内容(比如上面的案例);
- 弹窗组件中,打开弹窗后立刻让输入框聚焦(弹窗是 v-if 控制的,数据变了弹窗渲染是异步的);
- 所有需要「数据更新后操作 DOM」的场景,都用 $nextTick 兜底。
今日核心知识点总结(5 分钟复习,全部吃透)
✅ 1. 样式绑定
- class 绑定:对象语法(控制显示隐藏)、数组语法(批量绑定)
- style 绑定:对象语法,驼峰命名 css 属性,属性值支持变量
✅ 2. ref 关键字
- 作用:获取 DOM 元素 / 获取子组件实例
- 语法:
ref="名称"→this.$refs.名称 - 注意:挂载完成后才能使用,v-for 中获取的是数组
✅ 3. 组件通信(重中之重,必背)
- 父 → 子:
props接收父组件传值,只读不可改 - 子 → 父:
this.$emit('事件名', 参数)发布事件,父组件订阅接收 - 万能方案:事件总线 EventBus → 全局挂载
$bus,$emit发布,$on订阅,$off解绑 - 补充方案:ref 父组件主动获取子组件内容
✅ 4. 异步更新 DOM
- 原理:数据同步变,DOM 异步更,Vue 做了性能优化
- 解决方案:this.$nextTick (() => { 操作 DOM }) 必背!