reactive
如果想为在setup中定义的数据提供响应式
的特性,那么我们可以使用reactive函数
:
那么这是什么原因呢?为什么就可以变成响应式的呢?
- 这是因为当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集;
- 当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);
- 事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的;
数量:{{ state.count }}
reactive函数把对象参数处理过后,返回一个响应式对象,
响应式对象内部的key的值都是响应式的
缺陷:响应式变量被存放到对象中,不管在template还是在setup中使用都需要从对象中获取
reactive函数的参数类型
reactive API对传入的类型
是有限制的,它要求我们必须传入的是一个对象
或者数组
类型:
- 如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告;
ref API
ref函数 会返回一个可变的响应式对象ref对象
,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源;
- 它内部的值是在ref对象的
value
属性中被维护的;
这里有两个注意事项:
- 在template模板中使用ref对象的值时,Vue会自动帮助我们进行
解包
操作,所以我们并不需要在模板中通过ref.value
的方式使用,直接使用ref对象
就行 - 但是在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式;
template模板中的对ref对象的解包是一个浅层的解包
- 如果包裹着ref对象的是普通对象,vue不会自动帮我们把ref对象解包
- 如果包裹着ref对象的是reactive函数返回的可响应式对象,vue依然会自动帮我们把ref对象解包
数量:{{ count }}
数量:{{ info.count.value }}
数量:{{ state.count }}
readonly
我们通过reactive
或者ref
可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用
,但是不能被修改
,这个时候如何防止这种情况的出现呢?
Vue3为我们提供了readonly
的方法;
- readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不
能对其进行修改);
在开发中常见的readonly方法会传入三个类型的参数:
- 类型一:普通对象;
- 类型二:reactive返回的对象;
- 类型三:ref的对象;
readonly的使用
在readonly的使用过程中,有如下规则:
- readonly返回的对象都是不允许修改的;
- 但是经过readonly处理的原来的对象是允许被修改的;
- 比如 const info = readonly(obj),info对象是不允许被修改的;
- 当obj被修改时,readonly返回的info对象也会被修改;
- 但是我们不能去修改readonly返回的对象info;
- 其实本质上就是readonly返回的对象的setter方法被劫持了而已;
readonly的使用场景
在我们传递给其他组件数据时,往往希望其他组件使用我们传递的内容,但是不允许它们修改时,就可以使用readonly了;
需求:父组件App.vue传递给子组件Home.vue的响应式变量,在父组件内部修改了响应式变量的值,子组件中的数据要会响应式更新,但是子组件不能修改通过属性从父组件接收到的响应式变量的值
App.vue
Home.vue
名字: {{ name }}
信息: {{ info.name }}
所以传给子组件时可以把readonlyInfo2或readonlyInfo3只读变量传给子组件
在子组件内部只能使用,但不能修改readonlyInfo2中的属性的值或readonlyInfo3的值
但是在当前组件可以通过info2或info3修改属性的值,子组件中也会响应式更新
Reactive判断的API
isProxy
- 检查对象是否是由 reactive 或 readonly创建的 proxy。
isReactive
- 检查对象是否是由 reactive创建的响应式代理:
- 如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;
isReadonly
- 检查对象是否是由 readonly 创建的只读代理。
toRaw
- 返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。
shallowReactive
- 创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。
shallowReadonly
- 创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)。
案例
reactive: {{ info.friend.name }}
shallowReactive: {{ info1.friend.name }}
toRefs
如果我们使用ES6的解构
语法,对reactive返回的对象state进行解构获取值
,解构出来的变量name, age
,只是普通的值
,和原reactive对象state没有关系,
- 修改reactive返回的state对象的属性name,age的值,不会影响之前已经解构出的name, age变量,
- 修改变量name和age的值,也不会影响原reactive返回的state对象
- 在template模板中使用变量name, age,当变量name, age值改变时,页面中数据不会响应式更新
const info = reactive({ name: "why", age: 18 });
let { name, age } = info;
那么有没有办法让我们解构出来的属性是响应式的呢?
- Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref;
- 那么我们再次进行解构出来的 name 和 age 本身都是 ref对象;
- 这种做法相当于已经在state.name和ref.value之间建立了 链接,任何一个修改都会引起另外一个变化;
- 也就是修改name.value值,state.name也会发生改变,
- 修改state.name的值,name.value也会发生改变
- 如果在template模板中使用了变量name, age,name.value和age.value的值改变,页面中的数据也会响应式更新
- toRefs函数的参数必须是
reactive对象
const info = reactive({ name: "why", age: 18 });
let { name, age } = toRefs(info);
案例1:使用es6解构出的变量为普通的值:
解构出的变量的值是普通的值,不是响应式变量,值修改,页面中的数据不会响应式更新,与原reactive也没有关系
名字:{{ name }},年龄:{{ age }}
案例2:使用ref创建ref对象, 参数为reactive对象的某个属性的值:
ref函数只是获取了reactive对象的某个属性的值,ref函数返回的ref对象和原reactive对象没有其他关系,互不影响
名字:{{ name }},年龄:{{ age }}
案例3:使用toRefs解构出的变量为ref对象:
使用toRefs会将reactive返回的对象中的属性都转成ref,
从info解构出的变量都是ref对象,且跟原reactive对象的对应属性建立了链接,一个变量,另一个也会改变
名字:{{ name }},年龄:{{ age }}
toRef
如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法:
使用toRef函数返回一个ref对象,和原reactive对象中对应属性建立了链接,一个改变,另一个也改变
名字:{{ name }},年龄:{{ age }}
ref其他的API
unref
- 如果我们想要获取一个ref引用中的value,那么也可以通过unref方法:
- 如果参数是一个 ref,则返回内部值,否则返回参数本身;
- 这是 val = isRef(val) ? val.value : val 的语法糖函数;
isRef
- 判断值是否是一个ref对象。
shallowRef
- 创建一个浅层的ref对象;
triggerRef
- 手动触发和 shallowRef 相关联的副作用:
案例:ref函数创建的深层响应式对象和shallowRef创建的浅层响应式对象的区别
ref函数返回的ref对象,是深层响应的,其内嵌的对象的属性发生改变,页面中的数据也会响应式
shallowRef创建ref对象为浅层响应,
- 即只有其ref.value的值发生改变,页面才会响应式更新,
- ref.value.name发生改变,页面不会响应式更新更新
info:{{ info.name }}
info1:{{ info1.name }}
triggerRef的用法
如果希望浅层响应ref对象的内嵌对象的值改变时,页面上的数据响应式更新,
- 需要使用triggerRef函数手动触发浅层ref对象相关的副作用,
- 参数为要触发副作用浅层ref对象
info:{{ info.name }}
案例:unref和isRef的用法
customRef
创建一个自定义的ref,并对其依赖项跟踪
和更新触发
进行显示控制
:
- customRef函数的参数为一个
工厂函数
,此工厂函数有两
个参数,每个参数都是一个函数
- 第一个参数是
追踪函数track
, 你可以通过调用track函数,来决定什么时候收集依赖
- 第二个参数是
触发函数trigger
,你可以通过调用trigger函数,来决定什么时候触发所有的依赖
去更新数据
- 第一个参数是
- 通过上面两个函数,你可以决定什么时候收集依赖,什么时候触发更新
- 工厂函数需要返回一个
有get和set的对象
这里我们使用一个的案例:
- 对双向绑定的属性进行debounce(节流)的操作;
./hooks/useDebounceRef.js
import {
customRef
} from 'vue'
export default function (value) {
let timer = null
//customRef函数的参数为一个工厂函数,此工厂函数有两个参数,每个参数都是一个函数
//第一个参数是追踪函数track, 你可以通过调用track函数,来决定什么时候收集依赖
//第二个参数是触发函数trigger,你可以通过调用trigger函数,来决定什么时候触发所有的依赖去更新数据
//通过上面两个函数,你可以决定什么时候收集依赖,什么时候触发更新
//工厂函数需要返回一个有get和set的对象
return customRef((track, trigger) => {
return {
get() {
track() //在获取值的时候收集依赖
return value
},
set(newVal) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newVal
trigger() //在更新值后的1s后触发更新
}, 1000)
}
}
})
}
App.vue
{{message}}
此文档主要内容来源于王红元老师的vue3+ts视频教程