2.Vue3中数据绑定的实现
Vue3数据绑定从defineProperty改为Proxy,并且每一个功能抽离成一个模块,这里介绍源码中数据绑定的流程实现
Vue3数据绑定的实现
Vue3的数据绑定
不同于Vue2中的defineProperty方法,Vue3中采用proxy实现对数据的绑定,这种方式有以下好处:
- 无需重写getter和setter对数据进行劫持
- 无需实现 s e t 和 set和 set和delete方法实现对新增和删除属性的监控
- 不许对数组进行单独处理
Vue3通过reactive模块来实现数据绑定,通过effect模块实现数据的更新
const {effect,reactive } = Vue
//对数据进行绑定
const obj = {
name:'sx',
age:13,
address:{
num:30
},
flag:true
}
//这里只能传入对象,因为proxy只支持对象格式
const state = reactive(obj)
//数据响应
//effect函数会默认执行一次,后续数据发生变化会重新执行effect函数
effect(()=>{
app.innerHTML = state.name+'今年'+state.age+'岁了门牌号是'+state.address.num
})
setTimeout(()=>{
state.age++
},1000)
reactive模块的实现
根据其借助proxy的原理,可以实现reactive模块中的数据劫持
- 新建reactive.ts文件,用于实现主要逻辑
import {isObject} from "@vue/shared"
export function reactive(target){
//传入的值需要是一个对象,如果不是,则不需要进行绑定
if(!isObject(target)){
return target
}
}
- 原index.ts负责导出reactive模块
export {reactive} from "./reactive"
- 在reactive中实现初步的proxy代理
//实现最初的proxy
const proxy = new Proxy(target,{
get(target,key,receiver){
console.log('这个属性被取到了')
return target[key]
},
set(target,key,value,receiver){
console.log('这个属性改变了')
target[key] = value;
return true
}
})
return proxy
对数据进行绑定的意义,在于当数据变化时可以随时更新页面,这里通过get方法,当数据被访问时,就可以和effect绑定,当该属性触发set函数时,同样触发effect更新即可实现数据的劫持
但是这种设置却有很大的问题,那就是对象的this指向问题
- 对象一
let person = {
name:'sx',
age(){
return 18
}
}
const proxy = new Proxy(person,{
get(target,key,receiver){
console.log('这个属性被取到了')
return target[key]
},
set(target,key,value,receiver){
console.log('这个属性改变了')
target[key] = value;
return true
}
})
proxy.name
proxy.age
可以看到,这种对象用proxy可以顺利绑定每一个属性,但是下面这个却不能
- 对象二
let person = {
name:'sx',
get age(){
return this.name
}
}
const proxy = new Proxy(person,{
get(target,key,receiver){
console.log('这个属性被取到了')
console.log(key)
return target[key]
},
set(target,key,value,receiver){
console.log('这个属性改变了')
target[key] = value;
return true
}
})
proxy.age
这里访问proxy.age,按照设想和代码逻辑,当触发get之后,age属性会和effect进行绑定,当age属性值变化后,set中会让effect进行更新
但是这里的age依赖的却是name,也就是说,当name发生改变时,实际上age的返回值也会发生改变,但是proxy却监听不到,也就无法触发effect对age进行更新
这里的原因就是,age中的this指的是person对象,当通过proxy.age访问时,只会触发age属性的get,而不会触发name属性的get,自然当name变化时,effect不会触发更新
要解决这个办法,只需要把this的指向改为proxy对象即可,而Reflect对象即可完成这件事
let person = {
name:'sx',
get age(){
return this.name
}
}
const proxy = new Proxy(person,{
get(target,key,receiver){
console.log('这个属性被取到了')
console.log(key)
return Reflect.get(target,key,receiver)
},
set(target,key,value,receiver){
console.log('这个属性改变了')
target[key] = value;
return Reflect.set(target,key,value,receiver)
}
})
proxy.age
此时访问proxy.age,会同时触发age的get和name的get
事实上,每次进行Proxy绑定对象属性也会对性能有所消耗,因此要尽可能减少proxy的使用,reactive接收一个对象,如果用户多次输入同一个对象,则没有必要每次对对象进行Proxy,只需要从缓存里拿就好
- 情况一:用户输入同一数据对象
- 新建weakMap对象,判断是否存在该数据对象即可
const existing = reactiveMap.get(target) if(existing){ return existing }
- 在创建好proxy之后,存入weakMap中
reactiveMap.set(target,proxy)
- 情况二:用户第二次传入数据对象对应的proxy
const state = reactive(obj)
const state2 = reactive(state)
如果第二次传入的是proxy实例,则只需要触发其get方法验证,如果get能触发,则说明是proxy,直接返回即可
- 新建枚举对象
export const enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive'
}
- 触发get
if(target[ReactiveFlags.IS_REACTIVE]){
return target
}
- get中进行判断
if(key === ReactiveFlags.IS_REACTIVE){
return true
}
自此实现了Vue3中的数据绑定
更多推荐
所有评论(0)