前言
Vue3 提供了两种创建响应式数据的方式:ref
和 reactive
。它们有什么区别?在开发中该如何选择?本文将详细讲解它们的用法、适用场景,并介绍相关的辅助 API,如:
shallowRef
和shallowReactive
(浅层响应式)triggerRef
(手动触发 DOM 更新)customRef
(自定义响应式逻辑)readonly
(防止数据被修改)
读完本文,你将彻底理解 Vue3 的响应式系统,并能在项目中正确使用这些 API!
ref
ref
接受任意类型值,返回响应式对象,通过.value
访问
需要注意的是被ref
包装之后需要.value
来进行取值或赋值,模版除外
比如:
接收任意值
ref可以接收基本类型、引用类型的数据以及DOM的ref的属性值
- 如果ref接收的是一个基本类型的数据,那么
.value
保存的就是就是该原始值 - 如果ref接收的是一个引用类型的数据,那么
.value
保存的就是代理了该引用数据的proxy对象 - 无论是基本数据类型还是引用数据类型,最终返回的都是由 RefImpl 类构造出来的对象
响应式
ref
默认提供深层响应式,也就是说即使我们修改嵌套的引用类型数据,vue也能够检测到并触发页面更新
也就是说无论嵌套多深,vue都能够监听到数据的变化,说到监听数据变化,这就得提一下watch
方法了,虽然vue
能够监听到嵌套数据的变化,但是watch
函数如果监听的是ref
定义的引用类型数据,默认是不会开启深度监听的
虽然页面视图更新了,但是watch
是无法监听到数据变化的,想要监听到这一变化,我们需要手动开启深度监听
shallowRef
由于ref
默认是深层响应式,但有时候我们为了性能考虑,也可以通过 shallowRef 来放弃深层响应性。对于浅层 ref,只有 .value
的访问会被追踪。
修改属性值,虽然数据变化了,但是页面并不会更新,并且无法通过watch监听数据变化。
⚠️这里还有一点需要注意的是,ref与shallowRef最好不要一起使用,否则shallowRef会被影响
比如:
triggerRef
强制触发依赖于一个浅层 ref的副作用,这通常在对浅引用的内部值进行深度变更后使用。
当一个浅层ref的属性值发生改变又想触发页面更新时,可以手动调用triggerRef来实现
customRef
创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。
customRef()
接收一个工厂函数作为参数,该函数接收 track
和 trigger
两个函数作为参数,并返回一个带有 get
和 set
方法的对象。
- track:用于收集依赖项。在
get
方法中调用,收集该 ref 所依赖的响应式数据。 - trigger:用于触发更新。在
set
方法中调用,通知依赖项更新视图。
customRef
允许我们通过获取或设置一个变量的值时进行一些额外的操作,而不需要侦听这个变量进行额外的操作。
比如,我们可以使用cusromRef
实现一个自带防抖的响应式数据
reactive
reactive用于将一个引用类型数据声明为响应式数据,返回的是一个Proxy对象。
只接受引用类型数据
重要限制:reactive
只接受对象类型,基本类型会原样返回并产生警告
从上图我们还能看到,正常使用的reactive
返回的是一个Proxy
对象,也就是说reactive 实现响应式就是基于ES6 Proxy 实现的。
响应式
与ref
一样,reactive
默认也是深层响应式,并且watch
的监听是默认开启深度监听的
会丢失响应式的几个操作
- 对象引用发生变化
由于 Vue 的响应式跟踪是通过属性访问实现的,因此必须始终保持对响应式对象的相同引用。
- 解构
当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,也将丢失响应性
原始对象与代理对象
reactive()
返回的是一个原始对象的 Proxy代理对象,两者是不相等的
- 原始对象与代理对象是相互影响的
当原始对象里面的数据发生改变时,代理对象的数据也会发生变化;当代理对象里面的数据发生变化时,对应的原始数据也会发生变化
既然两者可以相互影响,那么修改原始对象会不会触发页面更新呢?🤔
答案是不会的,只有代理对象是响应式的,更改原始对象不会触发更新。因此,使用 Vue 的响应式系统的最佳实践是仅使用你声明对象的代理版本。
代理一致性
为保证访问代理的一致性,对同一个原始对象调用 reactive()
会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive()
会返回其本身:
依靠深层响应行,响应式对象内的嵌套属性依然是代理对象
shallowReactive
与shallowRef类似,shallowReactive也是用于声明一个浅层的响应式对象,用于性能优化处理
但如果同时修改顶层属性与嵌套属性的话,页面也是会同时更新顶层值与嵌套值的渲染,一般来说我们要避免这样使用,这会让数据流难以理解和调试
readonly
接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。常用于数据保护
总结
特性 | ref | reactive |
接受类型 | 任意类型 | 仅对象类型 |
访问方式 | 通过.value访问 | 直接访问属性 |
模板解包 | 自动解包(无需.value) | 无需解包 |
深层响应 | 默认支持 | 默认支持 |
性能优化 | shallowRef | shallowReactive |
watch | 对于引用类型,watch默认不会开启深度监听 | 默认开启深度监听 |
引用替换 | 保持响应(.value=新引用) | 完全丢失响应 |
解构处理 | 需保持.value引用 | 需配合toRefs |
适用场景 | 基本类型、组件模板引用、跨函数传递 | 复杂对象、状态管理、局部状态 |