阅读 749

蹭波热度--一步步实现 vue3 的基于Proxy的MVVM

vue3的mvvm实现原理是基于Proxy实现的,可比vue2的Object.defineProperty简明扼要很多,也很巧妙,demo不难,一步步实现吧,总共就一个js文件,不超过100行代码,要什么自行车。

1 勾勒出测试框架,大体分为几个部分 :

  1. 更新视图的方法

  2. 把数据变为响应式数据的方法

  3. 测试数据

  4. 改变测试数据 触发更新视图方法

    // 用最简化的模型来模式vue3的mvvm实现原理
    
    // 用这个方法来模式视图更新
    function updateView() {
        console.log('触发视图更新啦')
    }
    // 把原目标对象 转变 为响应式的对象
    function reactive(target) {
        // todo 具体如何转变,以及绑定视图更新方法
        let proxyed = new Proxy(target, options)
        return proxyed
    }
    
    // 测试数据
    let obj = {
        name: 'Ace7523',
        array: ['a', 'b', 'c']
    }
    
    // 把原数据转变响应式的数据
    let reactivedObj = reactive(obj)
    
    // 改变数据,期望会触发updateView() 方法 从而更新视图
    reactivedObj.name = 'change'
    复制代码

小结 准备工作算是做完了,接下来就是实现reactive方法,然后修改一下obj对象,看是否会触发updateView()方法

2 实现把原数据转变为响应式数据的reactive方法

  1. 利用 new Proxy(target, options) 对target对象,进行数据改造

  2. 完善 options

     // 用最简化的模型来模式vue3的mvvm实现原理
     
     // 用这个方法来模式视图更新
     function updateView() {
         console.log('触发视图更新啦')
     }
     // 把原目标对象 转变 为响应式的对象
     const options = {
         set(target, key, value, reciver) {
             updateView()
             return Reflect.set(target, key, value, reciver)
         },
         get(target, key, reciver) {
            return Reflect.get(target, key, reciver)
         },
         deleteProperty(target, key) {
             return Reflect.deleteProperty(target, key)
         }
     }
     function reactive(target) {
         // todo 具体如何转变,以及绑定视图更新方法
         let proxyed = new Proxy(target, options)
         return proxyed
     }
    
     // 测试数据
     let obj = {
         name: 'Ace7523',
         array: ['a', 'b', 'c']
     }
    
     // 把原数据转变响应式的数据
     let reactivedObj = reactive(obj)
    
     // 改变数据,期望会触发updateView() 方法 从而更新视图
     reactivedObj.name = 'change'
    复制代码

小结 其实本质上就是不用Object.defineProperty 而改用别的方式来重写对象的get set方法了,也就是Proxy 的方式

3 继续完善reactive方法, 不就是递归嘛,拿去

  1. 利用递归 对测试数据深层次代理
  2. 例如 本测试数据中 obj.array也是对象,所以在获取obj.array的时候,判断是否是对象,如果是的话,对这个对象也执行reactive方法,让这对象也成为响应式对象,以此类推,就可以把测试数据全部转变为响应式对象

添加的代码如下isObject()方法判断是否是对象,修改options.get()

function isObject(t) {
    return typeof t === 'object' && t !== null
}
// 把原目标对象 转变 为响应式的对象
const options = {
    set(target, key, value, reciver) {
        updateView()
        return Reflect.set(target, key, value, reciver)
    },
    get(target, key, reciver) {
        const res = Reflect.get(target, key, reciver)
        if(isObject(target[key])){
            return reactive(res)
        }
        return res
    },
    deleteProperty(target, key) {
        return Reflect.deleteProperty(target, key)
    }
}
复制代码

小结 这里比较巧妙,建议对比下vue2中的Object.defineProperty的方法对比着看。

4 继续完善reactive()方法 && 添加缓存 -- 完整代码如下

  1. 防止对数组添加数据时候,重复触发更新视图的方法

  2. 利用weakMap 记录原数据是否进行过代理,如果代理过,则返回记录值,不再重复代理

     // 用最简化的模型来模式vue3的mvvm实现原理
    
     // 用这个方法来模式视图更新
     function updateView() {
         console.log('触发视图更新啦')
     }
     function isObject(t) {
         return typeof t === 'object' && t !== null
     }
     // 把原目标对象 转变 为响应式的对象
     const options = {
         set(target, key, value, reciver) {
             if(target.hasOwnProperty(key)){
                 updateView()
             }
             return Reflect.set(target, key, value, reciver)
         },
         get(target, key, reciver) {
             const res = Reflect.get(target, key, reciver)
             if(isObject(target[key])){
                 return reactive(res)
             }
             return res
         },
         deleteProperty(target, key) {
             return Reflect.deleteProperty(target, key)
         }
     }
     // 用来做缓存
     const toProxy = new WeakMap()
    
     function reactive(target) {
         if(!isObject(target)){
             return target
         }
         // 如果已经代理过了这个对象,则直接返回代理后的结果即可
         if(toProxy.get(target)){
             return toProxy.get(target)
         }
         let proxyed = new Proxy(target, options)
         toProxy.set(target, proxyed)
         return proxyed
     }
    
     // 测试数据
     let obj = {
         name: 'Ace7523',
         array: ['a', 'b', 'c']
     }
    
     // 把原数据转变响应式的数据
     let reactivedObj = reactive(obj)
    
     // 改变数据,期望会触发updateView() 方法 从而更新视图
     reactivedObj.name = 'change'
    复制代码

小结 完整版代码暂时就这么多,没有考虑过多的边界条件,感兴趣的朋友可以复制出来玩玩看,改改测试数据看看会不会触发更新视图的方法~~~

关注下面的标签,发现更多相似文章
评论