前言
作为React的忠实粉丝和重度入读用户面对vue3.x的升级点也不得不稍稍get一下,也不算是尤其重要的一点吧,但至少是之一,就是这个响应式系统原理!Proxy!!!
Proxy对象
- set和deleteProperty中需要返回布尔类型的值,在严格模式下,如果返回false的话会出现Type Error
Reflect是反射的意思,在代码运行期间用来获取或者设置运行中的成员。 过去比较随意的把一些方法挂载到Object中,例如:Object.getProperty(),Reflect中也有对应的方法Reflect.getProperty(),方法的作用是一样的,如果在Reflect中有对应的方法,建议使用Reflect中的方法
'use strict'
const target={
foo:'xxxx',
bar:'yyyy'
}
const proxy = new Proxy(target,{
get(target,key,receiver){
return Reflect.get(...arguments)
},
set(target,key,receiver){
return Reflect.get(...arguments)
},
deleteProperty(target,key){
return Reflect.get(...arguments)
},
})
proxy.foo='zzs'
- Proxy和Reflect中使用的reaciver。Proxy中reciver是Proxy对象或者继承Proxy的对象。Reflect中的reciver:如果target对象中设置了getter,getter中的this指向reciver
const obj = {
get foo(){
console.log(this)
return this.bar
}
}
const proxy =new Proxy(obg,{
get(target,key,reciver){
if(key == 'bar'){
return 'value-bar'
}
return Reflect.get(target,key)
}
})
console.log(proxy.foo)
reactive功能
- 接收一个参数,判断这个参数是否是对象
reactive只能将对象转换成响应式对象,这是和ref不同的地方
- 创建拦截器对象handler,设置get/set/deleteProperty
- 返回Proxy对象
const isObject = val => val !== null && typeof val === 'object'
const convert = target => isObject(tartget) ? reactive(target) : target
/**
* 判断某个对象本身是否有指定的属性
* 这里要用到Object原型上的.hasOwnProperty
*/
const hasOwnProperty= Object.prototype.hasOwnProperty
const hasOwn = (target,key)=>hasOwnProperty.call(target,key)
export function reactive(tartget) {
// tartget如果不是对象直接返回
if (!isObject(tartget)) return tartget
//否则把target转换为代理对象
//捕获器、拦截器 trap
const handler = {
get(target, key, receiver) {
// 收集依赖
console.log('get', 'key')
/**
* 返回target中对应的key的值
* 如果key的值是对象的话,要在getter中递归收集下一级的依赖,还需要将里面的所有属性转换为响应式
*/
const result = Reflect.get(target, key, receiver)
return convert(result)
},
set(target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver)
let result = true
if (oldValue !== value) {
result = Reflect.set(target, key, value, receiver)
console.log('set',key,value)
}
return result
},
deleteProperty(target, key) {
/**
* 首先要判断当前target中是否有自己的key属性
* 如果有key属性,要判断key从target成功删除后触发更新
* 返回删除是否成功
*/
const hadkey = hasOwn(target,key)
const result = Reflect.deleteProperty(target, key)
if(hadkey&& result){
//触发更新
console.log('delete',key)
}
return result
},
}
return new Proxy(tartget, handler)
}
收集依赖
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
let total = 0
/**
* 首次加载首先会执行effect中的箭头函数
* 在箭头函数中又访问了product,product是一个reactive处理过的响应式对象,也就是代理对象
* 当访问product.price时会访问product.price的get方法,在get方法中要收集依赖,收集依赖的过程就是存储这个属性和回调函数
* 属性又和对象相关,所以在代理对象的get方法中首先会存储target目标对象,然后是target对象的属性price,然后把对应的箭头函数存储起来。这里是有对应关系的,目标对象、对应的属性以及对应的箭头函数。
* 在触发更新的时候再根据这个属性找到对应的函数
*/
effect(() => {
total = product.price * product.count
})
console.log(total)
product.price = 4000
console.log(total)
product.count = 1
console.log(total)
在依赖收集的过程中会创建三个集合:
- targetMap
new WeakMap(),targetMap的作用是用来记录目标对象和一个字典也就是目标对象的属性,使用的是弱引用的Map,也就是存储的key是对象,因为是弱引用,当目标失去引用后目标会销毁。targetMap的值是depsMap
- depsMap
new Map(),是一个字典,key是目标对象的属性名称,值是一个set集合,set集合中存储的元素不会重复
- dep
new Set(),里面存储的是effect函数,因为可以多次调用一个effect,并在effect中访问同一个属性,所以该属性会收集多次依赖对应多个effect函数
通过这种结构可以存储目标对象、目标对象的属性以及effect函数。一个属性可能对应多个函数。将来触发更新的时候可以根据目标对象的属性找到effect函数然后执行。
effect函数的实现
let activeEffect = null
export function effect(callback) {
// activeEffect用来记录callback
activeEffect = callback
/**
* 调用effect函数后会先执行一次callback
* 访问响应式对象属性,去收集依赖
*/
callback()
// 收集完依赖要置空
activeEffect = null
}
track函数实现
let targetMap = new WeakMap()
export function track(target, key) {
/**
* 收集依赖的track函数,首先要通过targetMap属性找到depsMap,
* 如果没有找到要给当前对象创建一个depsMap并添加到targetMap中,
* 如果找到了再根据当前的属性在depsMap中找到对应的dep。
* dep里存储的是effect函数,没有找到的话为当前属性创建对应的map并且存储到depsMap中。
* 如果找到当前属性对应的集合,则将当前的effect函数存储到相应的dep中。
*/
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect)
}
trigger实现
export function trigger (target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
// 遍历所有的effec函数然后挨个执行
dep.forEach(effect => {
effect()
})
}
}
ref
export function ref (raw) {
// 判断 raw 是否是ref 创建的对象,如果是的话直接返回
if (isObject(raw) && raw.__v_isRef) {
return
}
let value = convert(raw)
const r = {
// 创建标识属性,标识是ref创建的对象
__v_isRef: true,
get value () {
// 调用track收集依赖
track(r, 'value')
return value
},
set value (newValue) {
//判断新旧值是否相等
if (newValue !== value) {
raw = newValue
value = convert(raw)
trigger(r, 'value')
}
}
}
return r
}
reactive Vs ref
- ref可以把基本数据类型数据转成响应式对象,当获取数据时要使用.value属性,模板中可以省略value属性。reactive不能将基本数据类型转为响应式对象
- ref返回的对象,重新给value赋值后也是响应式的
- reactive返回的对象重新赋值丢失响应式,因为重新赋值的对象不再是代理对象
- reactive犯规的对象不可以结构,如果想要结构的话通过toRefs处理为响应式对象
toRefs
/**
* 首先判断接收的参数是否是一个reactive创建的对象,入股不是的话发送警告
*/
export function toRefs (proxy) {
const ret = proxy instanceof Array ? new Array(proxy.length) : {}
for (const key in proxy) {
ret[key] = toProxyRef(proxy, key)
}
return ret
}
// 将每一个属性转换为ref返回的对象
function toProxyRef (proxy, key) {
const r = {
__v_isRef: true,
get value () {
return proxy[key]
},
set value (newValue) {
proxy[key] = newValue
}
}
return r
}
computed
computed函数内部会通过effect监听getter内部响应式属性的变化,因为effect中执行getter的时候,访问数据的属性会去收集依赖,当数据变化后会重新执行effect函数,把getter结果再存储到result中
export function computed (getter) {
const result = ref()
effect(() => (result.value = getter()))
return result
}