阿里云上做网站,隆尧建设局网站,什么是网站网页主页,陕西旅游必去十大景点Set和Map类型的数据也属于异质对象#xff0c;它们有特定的属性和方法用来操作自身。因此创建代理时#xff0c;针对特殊的方法需要特殊的对待。
Vue 的ref 是基于reactive函数实现的#xff0c;它在其基础上#xff0c;增加了基本类型的响应性、解决reactive在解构时丢失… Set和Map类型的数据也属于异质对象它们有特定的属性和方法用来操作自身。因此创建代理时针对特殊的方法需要特殊的对待。
Vue 的ref 是基于reactive函数实现的它在其基础上增加了基本类型的响应性、解决reactive在解构时丢失响应性的问题及在模版字面量中自动脱落Ref.
1 代理Set和Map
function createReactive(obj,isShallow false) {return new Proxy(obj, {get(target, p, receiver) {track(target,p)const value Reflect.get(target,p,receiver)if (isShallow) return valuereturn typeof value object value ! null ? reactive(value) : value},// 省略其他代码})
}const proxyObj reactive(new Set())
// 报错 Method get Set.prototype.size called on incompatible receiver
console.log(proxyObj.size)上面代码报错receiver上不兼容size方法。这是因为上面代码中receiver 指向Proxy的代理对象它是没有size方法的。下面是对get方法改进。
get(target, p, receiver) {if (p size) return Reflect.get(target,p,target)// 省略其他代码
},但是改进后再执行proxyObj.add(1)又报错receiver 上不兼容add方法。因为是proxyObj执行add函数add函数里的this始终指向proxyObj。这是可以使用函数的bind方法来绑定函数中this的值。
get(target, p, receiver) {// 省略其他代码const value Reflect.get(target,p,receiver)if (typeof value function) return value.bind(target)if (isShallow) return valuereturn typeof value object value ! null ? reactive(value) : value
},1.1 建立响应联系
size属性是一个只读属性Set的add、delete会改变它的值。调用Set的add方法时如果元素已存在于Set中就不需要触发响应。调用Set的delete方法时如果元素不存在于Set中也不需要触发响应。
const mutableInstrumentation {add(key) {const target this.rawconst hadKey target.has(key)const res target.add(key)if (!hadKey res) {trigger(target, key, ADD)}return res},delete(key) {const target this.rawconst hadKey target.has(key)const res target.delete(key)if (hadKey res) {trigger(target, key,DELETE)}return res}
}
// Proxy 中的get代理
get(target, p, receiver) {// 省略其他代码track(target,p)if (mutableInstrumentation.hasOwnProperty(p)) {return mutableInstrumentation[p]}// 省略其他代码
},1.2 避免污染原始数据
const proxySet reactive(new Set())
const map new Map()
const proxyMap reactive(map)
proxyMap.set(set,proxySet)
effect(() {console.log(map.get(set).size)
})
console.log(----------------)
proxySet.add(1) // 触发响应上面原始数据具有了响应性这不符合需求原始数据应不具备响应性。产生这个的原因是在设置值时直接把响应体对象也添加进原始对象了。所以解决的关键在于设置值时如果该对象是响应体对象则取其目标对象。
// mutableInstrumentation 对象的set方法
set(key,value) {const target this.rawconst had target.has(key)const oldValue target.get(key)target.set(key,value.raw || value) // 去目标对象if (!had) {track(target, key, ADD)} else if (oldValue ! value (oldValue oldValue || value value)) {trigger(target,key,SET)}
}1.3 处理forEach
1forEach 只与键值对的数量有关所以当forEach被调用时让ITERATE_KEY与副作用函数建立联系。
2当set方法设置的属性存在时但属性值不同时也应该触发forEach。
3forEach函数的参数callback它是有原始对象调用的这意味着callback函数中的value及key两个参数不具有响应性但是它们应该都具备响应性需要将这两个参数转成响应体。
// mutableInstrumentation 对象的forEach方法
forEach(callback) {const target this.rawconst wrap (val) typeof val object ? reactive(val) : valtrack(target,ITERATE_KEY)target.forEach((v,k) {callback(wrap(v),wrap(k),this)})
}function trigger(target,p,type,newValue) {const map effectMap.get(target)if (map) {// 省略其他代码if (type ADD || type DELETE || (type SET Object.prototype.toString.call(target) [object Map])) {// 省略其他代码}// 省略其他代码 }
}1.4 迭代器方法
集合类型有三个迭代器方法entries、keys、values,还可以使用 for...of进行迭代。
使用for...of迭代一个代理对象时内部会调用[Symbol.iterator]()方法返回一个迭代器。迭代产生的值不具备响应性所以需要把这些值包装成响应体。可迭代协议指一个对象实现了Symbol.iterator方法迭代器协议是指一个对象实现了next方法。而entries方法要求返回值是一个可迭代对象即该对象要实现了Symbol.iterator方法。values 方法返回的仅是Map的值而非键值对。keys 方法与上面不同的是调用set时如果非添加值则不应该触发响应。
function trigger(target,p,type,newValue) {const map effectMap.get(target)if (map) {// 省略其他代码if (type ADD || type DELETE Object.prototype.toString.call(target) [object Map]) {const tempSet map.get(MAP_KEY_ITERATE_KEY)tempSet tempSet.forEach(fn {if (activeEffectFun ! fn) addSet.add(fn)})}addSet.forEach(fn fn())}
}const mutableInstrumentation {// 省略其他代码[Symbol.iterator]: iterationMethod,entries: iterationMethod,values: valueIterationMethod,keys: keyIterationMethod
}function iterationMethod() {const target this.rawconst itr target[Symbol.iterator]()const wrap (val) typeof val object val ! null ? reactive(val) : valtrack(target,ITERATE_KEY)return {next() {const {value,done} itr.next()return {value: value ? [wrap(value[0]),wrap(value[1])] : value,done}},[Symbol.iterator]() {return this}}
}
function valueIterationMethod() {const target this.rawconst itr target.values()const wrap (val) typeof val object val ! null ? reactive(val) : valtrack(target,ITERATE_KEY)return {next() {const {value,done} itr.next()return {value: wrap(value),done}},[Symbol.iterator]() {return this}}
}
function keyIterationMethod() {const target this.rawconst itr target.keys()const wrap (val) typeof val object val ! null ? reactive(val) : valtrack(target,MAP_KEY_ITERATE_KEY)return {next() {const {value,done} itr.next()return {value: wrap(value),done}},[Symbol.iterator]() {return this}}
}
2 原始值的响应方案ref
Proxy 的代理目标必须是非原始值如果要让原始值具有响应性那么要对它进行包装。Vue3 的ref函数就负责这个工作。
function ref(val) {const wrapper {value: val}// 为了区分数据是经过ref包装的还是普通对象Object.defineProperty(wrapper,_v_isRef,{value: true})return reactive(wrapper)
}2.1 reactive 解构时丢失响应性
const proxyObj reactive({name: hmf,num: 1})
const obj {...proxyObj} // obj 不再具有响应性。这是因为解构时
{…proxyObj} 等价于 {name: hmf,num: 1}要让obj 具有响应性则需要使其属性值为一个对象。如下所示
const obj {name: {get value() {return proxyObj.name},set value(val) {proxyObj[name] val}},num: {get value() {return proxyObj.num},set value(val) {proxyObj[num] val}}
}ref函数优化如下
function toRefs(obj) {const ret {}for (const key in obj) {ret[key] toRef(obj,key)}return ret
}function toRef(obj,key) {const wrapper {get value() {return obj[key]},set value(val) {obj[key] val}}Object.defineProperty(wrapper,_v_isRef,{value: true})return wrapper
}2.2 自动脱ref
经过toRefs处理的对象都需要通过对象的value属性来访问例如
const proxyObj ref({name: hmf,num: 1})
console.log(proxyObj.name.value)
proxyObj.name.value hi
访问任何属性都需要通过value属性访问这增加了用户的心智负担。我们需要自动脱ref的能力即上面proxy.name就可直接访问。
function proxyRefs(target) {return new Proxy(target, {get(target, p, receiver) {const value Reflect.get(target,p,receiver)return value._v_isRef ? value.value : value},set(target, p, newValue, receiver) {const value target[p]if (value._v_isRef) {value.value newValuereturn true}return Reflect.set(target,p,newValue,receiver)},})
}