Vue3的响应式
大约 5 分钟
回顾 Vue2 的响应式
Vue2 的响应式基于 Object.defineProperty(...) 实现。
// 创建一个user1对象
let user1 = {
name : 'jack'
}
// 创建一个user2对象
let user2 = {}
// 给user2扩展一个属性name
Object.defineProperty(user2, 'name', {
// 数据代理
get(){
console.log('get方法执行了。')
return user1.name
},
// 数据劫持
set(value){
// 改数据
user1.name = value
// 同时在这里可以进行响应式处理,更新页面
console.log('这里进行响应式处理,页面已经更新.')
}
})Vue2 响应式的问题:
后期属性的增加或删除没有响应式。
如需要新增响应式的属性需要调用
$set|set这个API。数组中元素的修改不能响应式。
数组中元素修改的响应式需要调用特定的API:push、shift、unshift...
- 根本问题是
Object.defineProperty不是完备的。
Vue3 的响应式
与 Vue2 不同,Vue3 以 Proxy 为核心实现响应式。
Vue3 将响应式的实现以API的形式提供,需要响应式的数据则需要用特定的API进行包装。
setup() {
// data
// 默认都没有响应式。
let mess = "welcome";
let user = {
name : 'jack',
age : 20
}
// 返回对象
return {mess, user}
}ref 实现的响应式
这种响应式适用于简单类型,提供 ref(value0) 进行包裹后,value 即具有响应式了。
ref 包裹之后,会返回一个 RefImpl 对象(引用对象),这个对象中的 value 属性即是被包裹的添加了响应式的属性。
即:value0 无响应式,直接修改 value0 不会更新。
refImpl.value 有响应式,且 refImpl.value == value0。通过修改 refImpl.value,可以获得响应式更新。
// 需要先导入 ref 这个函数
import { ref } from 'vue'
setup() {
// data
// 基本数据类型通过 Object.defineProperty 实现响应式
let counterRefImpl = ref(100)
let useObj = {
name : 'jack',
age : 20,
addr : {
city : '北京',
street : '大兴区凉水河二街'
}
}
// 如果ref包裹的是一个对象,响应式通过 Object.defineProperty + Proxy 实现的。
// Proxy 实现的响应式,对象中的对象,对象中的对象,都有响应式处理。(底层是递归处理了)
let userRefImpl = ref(userObj)
// methods
function modifyUser(){
// 这种修改是有响应式处理的(基于 Object.defineProperty)。
counterRefImpl.value = 200
// 对于userRefImpl对象来说,应该怎么改,才能有响应式呢?
// 如果采用这种value直接赋值的方式,表示这种响应式是基于 Object.defineProperty 的。
/* userRefImpl.value = {
name : '张三',
age : 50
} */
// userRefImpl.value 是一个代理对象Proxy(代理了 userObj)。
userRefImpl.value.name = '李四'
userRefImpl.value.age = 90
// 修改地址信息
userRefImpl.value.addr.city = '邯郸'
userRefImpl.value.addr.street = '太极二路'
}
// 返回对象
return {counterRefImpl, userRefImpl, modifyUser}
}当一个对象是 RefImpl 对象(引用对象)时,Vue3 会自动访问它的 value 属性。即模板中使用这个对象时,不用显式 .value。
<!-- 在模板语法当中访问RefImpl对象的时候,不需要手动 .value -->
<h2>计数:{{counterRefImpl}}</h2>
<h2>姓名:{{userRefImpl.name}}</h2>小结:
reactive 实现的响应式
reactive 不能包裹基本数据类型,会报错。reactive 是专用于添加对象的响应式的函数。
reactive 将一个对象包裹后,直接返回 Proxy 对象,这个Proxy对象实现了对原对象的响应式(完备的)。
// 需要先导入 reactive 这个函数
import { reactive } from 'vue'
setup() {
// data
let userObj = {
name : '张三',
age : 30,
counter : 200,
addr : {
city : '北京',
street : '大兴区凉水河二街',
a : {
b : {
c : 111
}
}
},
courses : ['语文', '数学', '英语']
}
// reactive函数,可以将一个对象直接包裹。实现响应式。底层是生成一个Proxy对象。
let userProxy = reactive(uerObj)
// methods
function modifyUser(){
// userProxy 是一个proxy,不需要再 .value,直接访问属性即可
userProxy.name = '李四'
userProxy.age = 50
userProxy.counter = 500
userProxy.addr.city = '邯郸'
userProxy.addr.street = '太极二路'
userProxy.addr.a.b.c = 666
}
// 返回对象
return {userProxy, modifyUser}
}由于是基于 Proxy 实现的响应式,故是完备的,属性的增删改查、数组元素的更改都是响应式的。
自定义的类ref函数实现响应式
import {customRef} from 'vue'
// 创建一个防抖 ref,通过这个ref创建的响应式更新具有防抖效果
// useDebouncedRef函数 就是自已定义的 类ref函数
function useDebouncedRef(value){
// 自定义的ref这个函数体当中的代码不能随便写,必须符合ref规范。
// 通过调用customRef函数来获取一个自定义的ref对象。
// 调用customRef函数的时候必须给该函数传递一个回调:这个回调可以叫做factory
// 对于这个回调函数来说,有两个非常重要的参数: track是追踪。trigger是触发。
// 防抖的标识
let t
const x = customRef((track,trigger)=>{
// 对于这个factory回调来说,必须返回一个对象,并且对象中要有get方法
// track、trigger 是成对使用的
return {
// 模板语句中只要使用到该数据,get会自动调用。
get(){
//告诉Vue去追踪这个value的变化
track()
return value
},
//当模板语中修改该数据的时候,set会自动调用。
set(newValue){
// 实现防抖
// 1. 一旦有更改,先取消先前的定时
clearTimeout(t)
// 2. 重新开始定时
t = setTimeout(()=>{
value = newValue
// 触发(通知去调用get方法)
trigger()
},1000)
}
}
})
//返回自定义的ref对象实例。
return x
}
// 使用自定义的类ref进行包装,同样具备响应式。
let name = useDebouncedRef('test') // 类 ref('test')