likes
comments
collection
share

Vue3手写系列之reactive

作者站长头像
站长
· 阅读数 16

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函数,就可以得出输出代理对象的结果。

Vue3手写系列之reactive

提示

代码中先判断是否是对象,是因为proxy只能做对象代理。判断是否是对象也很简单,代码如下:

// 公共方法文件
export const isObject = (value) => {
    return typeof value === 'object' && value !== null
}

this指向

上面的对象代理很容易写出来,简单是简单,但是肯定会存在问题的呀。。。

就比如下面这个:

在我们进行代理的时候,我们可以看到在输出proxy的fn时,proxy取值时只输出了state对象中fn这个key,并没有读取fn中使用的age这个属性。

Vue3手写系列之reactive

我们需要监听对象函数中使用的属性时,就需要使用到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来设置,最后再将值返回。 代码如下: Vue3手写系列之reactive

这样我们就可以解决对象中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
评论
请登录