Vue3手写系列之reactive
hello 大家好,🙎🏻♀️🙋🏻♀️🙆🏻♀️
我是一个热爱知识传递,正在学习写作的作者,ClyingDeng
凳凳!
好久不见哈!
今天要给大家带来的是vue3的reactive手写实现,这个不难,大家跟上哟~
简单案例如下:
<script src="../../../../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
<script>
const { reactive } = VueReactivity
const obj = {
name: 'dy',
age: 25,
get fn() {
return this.age
}
}
const state = reactive(obj)
</script>
在index.html
文件中,引入vue中的reactivity
的依赖,获取其对外暴露的reactive这个API。再定义一个对象,并代理这个对象(reactive)。在对对象进行一系列赋值取值操作。
本文讲解reactive对state对象进行代理时,一个对象代理多次、代理对象被再次代理,两者是否还相等问题的处理。
new 一个 proxy
vue3 采用了 ES6 中的 proxy API,和 defineProperty 类似。两者都是代理,只是proxy
直接是创建了一个对象的代理,实现代理对象的基本操作;而 defineProperty
呢,它是通过拦截对象的属性,对其属性进行基本操作。
那么我们一开始,肯定就需要先实现一个对象的简单代理吖。
来,直接上proxy:
// 自己手写 reactive.js 文件
export function reactive(target) {
// 需要首先判断是否是对象,不是直接返回
const proxy = new Proxy(target, {
get(target, key, receiver) {
return target[key]
},
set(target, key, value, receiver) {
target[key] = value
return true
}
})
return proxy
}
看吧,真的照搬vue2的defineProperty实现一个基础的proxy,并不难。直接在index.html
中引入reactive
函数,就可以得出输出代理对象的结果。
提示
代码中先判断是否是对象,是因为proxy只能做对象代理。判断是否是对象也很简单,代码如下:
// 公共方法文件
export const isObject = (value) => {
return typeof value === 'object' && value !== null
}
this指向
上面的对象代理很容易写出来,简单是简单,但是肯定会存在问题的呀。。。
就比如下面这个:
在我们进行代理的时候,我们可以看到在输出proxy的fn时,proxy取值时只输出了state对象中fn这个key,并没有读取fn中使用的age这个属性。
我们需要监听对象函数中使用的属性时,就需要使用到Reflect
这个API。
在get中使用Reflect.get(target, key, receiver)
代替直接返回的target[key]
。receiver
为调用 target 提供的 this 值,在此即指state中被代理的对象obj。
这样,我们就可以监听到fn函数中使用的代理对象的其他属性了。
// 自己手写 reactive.js 文件
const proxy = new Proxy(target, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
return result
}
})
同样,我们在设置值的时候也需要使用Refelct来设置,最后再将值返回。
代码如下:
这样我们就可以解决对象中this使用监听属性变化的问题啦🫰🫰🫰~
代理对象被再次代理
结论:代理对象被再次代理,应该相同。 代理对象被再次代理是个什么情况呢? 是这样的,我们state代理对象被另一个变量再次代理,代码如下:
// index.html 文件
// 引入自己实现的reactive功能函数
<script src="./reactivity.global.js"></script>
<script>
const { reactive } = VueReactivity
const obj = {
name: 'dy',
age: 25,
get fn() {
return this.age
}
}
const state = reactive(obj)
// 代理对象被再次代理情况
const state1 = reactive(state) // state代理了obj对象,state这个对象又被state1代理
console.log(state === state1)
</script>
根据我们之前完成的基础版reactive,输出得到的结果必然为false
。
但其实使用vue自身的reactive函数,得出的结果是true
。
在此,我们就需要在reactive执行的时候判断该对象是否被代理过,如果被代理过直接返回代理对象。
那么,该怎么做呢🤨🤨🤨?
定义一个枚举:
const enum ReactiveFlag {
IS_REACTIVE = '_v_isReactive'
}
在函数执行的时候判断该对象上是否存在_v_isReactive
属性。在判断的时候,就会走proxy中的get方法。
if (target[ReactiveFlag.IS_REACTIVE]) return target
第一次代理的完成之后才会生成proxy,存在get和set方法。所以如果不是第一次代理对象,上述代码将不会执行。是代理对象的话,将会走到get方法中。
这样我们在get方法中可以通过判断取值的key是否是_v_isReactive
,来确认是否是代理对象。是的话,执行完get返回true,if条件为true,则会直接返回该代理对象。
// 自己要实现的reactive功能函数
export function reactive(target) {
if (!isObject(target)) return
if (target[ReactiveFlag.IS_REACTIVE]) return target // 如果存在_v_isReactive属性,则表示该对象为代理对象
const proxy = new Proxy(target, {
get(target, key, receiver) {
if (key === ReactiveFlag.IS_REACTIVE) return true
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
return result
}
})
return proxy
}
一个对象被代理多次
结论:一个对象代理多次,应该相同🤔🤔🤔。
情况是这样的:
const data = {
name: '111'
}
const state2 = reactive(data)
const state3 = reactive(data)
console.log(state2 === state3) // 应为true
在我们上述情况完成的reactive输出结果为false,肯定是不正确的。
那么我们应该这样做🧐
通过WeakMap将原对象和代理对象关联,如果在WeakMap内读取对象,存在时,返回该存在的代理对象。
const reactiveMap = new WeakMap() // 用作存储
export function reactive(target) {
if (!isObject(target)) return
if (target[ReactiveFlag.IS_REACTIVE]) return target
const exisitingProxy = reactiveMap.get(target)
if (exisitingProxy) return exisitingProxy // 当前目标对象的代理值
const proxy = new Proxy(target, {
get(target, key, receiver) {
if (key === ReactiveFlag.IS_REACTIVE) return true
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
return result
}
})
reactiveMap.set(target, proxy) // 原对象和代理对象关联
return proxy
}
这样,我们的reactive基本功能就实现的差不多啦!🫶🫶🫶
感兴趣的朋友可以关注 手写vue3系列 专栏或者点击关注作者哦(●'◡'●)!。 如果不足,请多指教。
转载自:https://juejin.cn/post/7144177440674283528