目录
一、ref和reactive的区别
1. ref
2. reactive
3、核心区别
4、原理差异
1. ref 的实现
2. reactive 的实现
5、常见误区
二、计算属性(Computed)
1. 基本用法
2. 计算属性的 setter 用法
3. 计算属性的特点
三、Watch 的使用
1. 基础监听(watch 函数)
2. 深度监听(对象属性)
3. 立即执行监听(immediate 选项)
4. watchEffect 自动追踪依赖
四、计算属性 vs Watch 的选择
五、生命周期API
六、组合式API下的组件间数据传递
一、父传子:通过 props 传递数据
二、子传父:通过 emits 触发事件
三、双向绑定:v-model 语法糖
四、provide/inject:跨层级通信
五、使用 $parent 和 $children(不推荐)
六、使用事件总线或状态管理
总结
七、组合式API下的模板引用
一、组合式 API 中的模板引用
示例:获取 DOM 元素
示例:获取组件实例
二、使用 $refs(选项式 API 风格)
三、动态绑定引用
四、组件引用与跨组件访问
五、注意事项
总结
ref
number
、string
、boolean
)或引用类型(如对象、数组)。const count = ref(0)
。.value
访问值(如 count.value
)。reactive
const state = reactive({ count: 0 })
。state.count
)。特性 | ref |
reactive |
---|---|---|
适用类型 | 任意类型(值类型或引用类型) | 仅对象或数组(引用类型) |
创建方式 | ref(initialValue) |
reactive(object) |
访问方式 | 通过 .value 访问(如 count.value ) |
直接访问属性(如 state.count ) |
响应式原理 | 使用 Object.defineProperty() 或 Proxy |
仅使用 Proxy |
深层响应式 | 引用类型(对象 / 数组)自动深层响应式 | 自动深层响应式 |
解构后响应性 | 解构后失去响应性,需使用 toRefs 保留 |
解构后仍保持响应性 |
性能 | 基本类型(值类型)更高效 | 对象 / 数组的操作更高效 |
ref
的实现value
属性: class RefImpl {
constructor(value) {
this._value = value; // 原始值
this.value = value; // 响应式值
// 通过 getter/setter 拦截 value 的读写
}
}
Object.defineProperty()
拦截 value
的读写。reactive()
转为深层响应式对象。reactive
的实现Proxy
拦截整个对象的属性读写: function reactive(target) {
return new Proxy(target, {
get(target, key) {
// 依赖收集
return target[key];
},
set(target, key, value) {
// 触发更新
target[key] = value;
return true;
}
});
}
ref
在模板中无需 .value
:
Vue 3 模板会自动解包 ref
,直接使用 {{ count }}
即可。
reactive
不能替代 ref
:
reactive
无法处理基本类型(如 number
),必须使用 ref
。
解构 reactive
的注意事项:
解构后属性的响应性依赖于原始对象,若重新赋值会失去响应性。
计算属性是 Vue3 中用于处理复杂数据逻辑的重要特性,基于响应式依赖进行缓存,只有当依赖的值发生变化时才会重新计算。
计算属性通过 computed
来定义,接受一个 getter 函数或包含 getter/setter 的对象:
原值: {{ firstName }} {{ lastName }}
计算属性值: {{ fullName }}
计算属性(缓存演示): {{ cachedComputed }}
普通函数: {{ getFullName() }}
计算属性不仅可以读取,还可以通过对象形式定义 setter 实现双向绑定:
名: {{ firstName }}
姓: {{ lastName }}
Watch(监听器)用于监听数据变化,并在变化时执行回调函数,适用于需要在数据变化时触发副作用的场景。
输入内容: {{ message }}
对于对象类型的数据,需要深度监听才能捕获属性变化:
注意当监听的是reactive对象时,watch的第一个参数 需要写为函数的形式。
当监听的是ref对象时,watch的第一个参数为普通的变量名的形式。
监听目标 | 第一个参数写法 | 是否需要 deep 选项 |
---|---|---|
ref 变量 | count |
否 |
ref 对象的单个属性(精确监听) | () => user.value.age |
否 |
reactive 对象的属性 | () => user.name |
否 |
整个 reactive 对象 | () => user |
是 |
多个数据源 | [count, () => user.age] |
部分情况需要 |
计算属性式的监听值 | () => obj.a * obj.b |
否 |
当前状态: {{ status }}
watchEffect
会自动追踪回调函数中的响应式依赖,适用于需要立即执行且自动追踪依赖的场景:
计算结果: {{ result }}
场景 | 计算属性 | Watch |
---|---|---|
数据转换 | ✅(推荐) | ❌ |
异步操作 | ❌ | ✅(推荐) |
副作用(如 API 调用) | ❌ | ✅(推荐) |
多依赖组合 | ✅(推荐) | ✅ |
立即执行 | ❌ | ✅(通过 immediate 选项) |
阶段 | 选项式 API | 组合式 API | 触发时机 |
---|---|---|---|
创建阶段 | beforeCreate |
相当于setup | 实例创建之前,数据观测和事件配置尚未初始化。 |
created |
实例创建完成,数据观测和事件已配置,但 DOM 尚未生成。 | ||
挂载阶段 | beforeMount |
onBeforeMount |
模板编译完成,即将开始渲染 DOM 前。 |
mounted |
onMounted |
DOM 挂载完成后触发,可访问 DOM 元素。 | |
更新阶段 | beforeUpdate |
onBeforeUpdate |
数据更新导致组件重新渲染前,此时 DOM 尚未更新。 |
updated |
onUpdated |
组件更新完成,DOM 已同步更新后触发。 | |
卸载阶段 | beforeUnmount |
onBeforeUnmount |
组件即将卸载前,可用于清理定时器、事件监听等资源。 |
unmounted |
onUnmounted |
组件已卸载,所有子组件也已卸载完成。 | |
错误处理 | errorCaptured |
onErrorCaptured |
捕获到组件或子组件的错误时触发,可用于错误日志记录。 |
服务器渲染 | serverPrefetch |
onServerPrefetch |
(仅服务端渲染)组件在服务器端渲染前触发,用于数据预获取。 |
父组件通过 defineProps
声明并传递数据,子组件通过 defineProps
接收。
父组件:
子组件:
Received message: {{ message }}
Received count: {{ count }}
子组件通过 defineEmits
声明事件,通过 emit
触发,父组件监听事件并处理。
子组件:
父组件:
通过 v-model
实现父子组件的双向数据流动,子组件通过 update:propName
事件更新父组件数据。
父组件:
子组件:
父组件通过 provide
提供数据,任意层级的子组件通过 inject
获取数据,无需逐级传递。
祖先组件:
任意层级子组件:
Shared data: {{ sharedData }}
如果想让子孙组件修改父组件中的参数,可以把方法写在父组件中,通过provide和inject把方法传递给子孙组件,子孙组件调用这个方法来修改参数
$parent
和 $children
(不推荐)通过 $parent
访问父组件实例,通过 $children
访问子组件实例。这种方式破坏了组件封装性,不推荐在大型项目中使用。
子组件:
对于复杂场景,可使用第三方库(如 Pinia、Vuex)或自定义事件总线实现组件通信。
通信方式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
props | 父 → 子单向数据流 | 简单直观,类型安全 | 只能单向传递 |
emits | 子 → 父事件触发 | 语义明确,便于调试 | 多层级时需逐级传递 |
v-model | 双向数据绑定 | 语法简洁,代码量少 | 依赖特定事件名(update:propName) |
provide/inject | 跨层级数据共享 | 无需逐级传递,支持响应式 | 依赖注入键,调试困难 |
\(parent/\)children | 直接访问组件实例(不推荐) | 快速获取实例 | 破坏封装性,耦合度高 |
在实际开发中,推荐优先使用 props 和 emits 实现单向数据流,复杂场景使用 provide/inject 或状态管理库。
模板引用允许在 JavaScript 中直接访问 DOM 元素或组件实例,通常用于
在组合式 API 中,模板引用通过 ref()
创建,并通过 v-bind
绑定到元素或组件上。
$refs
(选项式 API 风格)在组合式 API 中,也可以通过 getCurrentInstance
访问 $refs
:
容器
模板引用可以动态绑定到不同元素:
元素 A
元素 B
在子组件中暴露方法供父组件调用:
引用值的延迟
模板引用在初始渲染时为 null
,只有在组件挂载后才会指向实际元素。建议在 onMounted
或之后访问。
与响应式数据的区别
模板引用不是响应式的,其值变化不会触发重新渲染。
组合式 API 与选项式 API 的区别
ref()
创建引用变量。this.$refs
访问引用。函数式组件的引用
函数式组件需要显式接受 ref
参数并通过 forwardRef
转发。
特性 | 说明 |
---|---|
创建引用 | 使用 ref(null) 创建,初始值为 null 。 |
绑定到元素 | 使用 ref="refName" 绑定到 DOM 元素或组件。 |
访问引用 | 通过 refName.value 访问实际元素或组件实例。 |
组件暴露 | 使用 defineExpose() 暴露子组件的方法和属性。 |
动态引用 | 支持动态绑定到不同元素,值会自动更新。 |