• 技术文章 >web前端 >Vue.js

    原理详解:Vue3中reactive和ref的区别

    青灯夜游青灯夜游2022-09-27 20:32:08转载528
    一文带你看懂vue3中最重要的API——ref和reactive,还在纠结用哪个么,想把Vue3用好快来看

    大前端零基础入门到就业:进入学习

    vue2的响应式是通过Object.defineProperty 方法,劫持对象的gettersetter,在getter中收集依赖,在setter中触发依赖,但是这种方式存在一些缺点:

    【相关推荐:vuejs视频教程

    vue3响应式的实现

    Proxy对象

    针对Object.defineProperty的弊病, 在 ES6 中引入了一个新的对象——Proxy(对象代理)

    Proxy 对象:

    用于创建一个对象的代理,主要用于改变对象的某些默认行为,Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。基本语法如下:

    /*
     * target: 目标对象
     * handler: 配置对象,用来定义拦截的行为
     * proxy: Proxy构造器的实例
     */
     var proxy = new Proxy(target,handler)

    拦截get,取值操作

    var proxy = new Proxy({}, {
      get: function(target, propKey) {
        return 35;
      }
    });
    
    proxy.time // 35
    proxy.name // 35
    proxy.title // 35

    可以拦截的操作有:

    函数操作
    get读取一个值
    set写入一个值
    hasin操作符
    deletePropertyObject.getPrototypeOf()
    getPrototypeOfObject.getPrototypeOf()
    setPrototypeOfObject.setPrototypeOf()
    isExtensibleObject.isExtensible()
    preventExtensionsObject.preventExtensions()
    getOwnPropertyDescriptorObject.getOwnPropertyDescriptor()
    definePropertyObject.defineProperty
    ownKeysObject.keys() Object.getOwnPropertyNames()和Object.getOwnPropertySymbols()
    apply调用一个函数
    constructnew一个函数

    那么使用Proxy可以解决Vue2中的哪些问题,总结一下:

    递归代理

    var target = {
      a:1,
      b:{
        c:2,
        d:{e:3}
      }
    }
    var handler = {
      get:function(target, prop, receiver){
        console.log('触发get:',prop)
        return Reflect.get(target,prop)
      },
      set:function(target,key,value,receiver){
        console.log('触发set:',key,value)
        return Reflect.set(target,key,value,receiver)
      }
    }
    var proxy = new Proxy(target,handler)
     
    proxy.b.d.e = 4 
    // 输出  触发get:b , 由此可见Proxy仅代理了对象外层属性。

    以上写法只代理了对象的外层属性,所以要想深层代理整个对象的所有属性,需要进行递归处理:

    var target = {
      a:1,
      b:{
        c:2,
        d:{e:3}
      },
      f: {z: 3}
    }
    var handler = {
      get:function(target, prop, receiver){
        var val = Reflect.get(target,prop)
        console.log('触发get:',prop)
        if(val !== null && typeof val==='object') {
            return new Proxy(val,handler) // 代理内层属性
        }
        return Reflect.get(target,prop)
      },
      set:function(target,key,value,receiver){
        console.log('触发set:',key,value)
        return Reflect.set(target,key,value,receiver)
      }
    }
    var proxy = new Proxy(target,handler)
     
    proxy.b.d.e = 4 
    // 输出  触发get b,get d, get e

    从递归代理可以看出,如果要代理对象的内部属性,Proxy可以只在属性被调用时去设置代理(惰性),访问了e,就仅递归代理b下面的属性,不会额外代理其他没有用到的深层属性,如z

    关于 Reflect 的作用和意义

    Function.prototype.apply
    Object.defineProperty

    vue3的reativeref

    Vue3 的 reactive 和 ref 正是借助了Proxy来实现。

    reactive

    作用:创建原始对象的响应式副本,即将「引用类型」数据转换为「响应式」数据

    参数: reactive参数必须是对象或数组

    reative函数实现:

    // 判断是否为对象
    const isObject = val => val !== null && typeof val === 'object';
    // 判断key是否存在
    const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key);
    
    export function reactive(target) {
        // 首先先判断是否为对象
        if (!isObject(target)) return target;
    
        const handler = {
            get(target, key, receiver) {
                console.log(`获取对象属性${key}值`)
                // 收集依赖 ...
                const result = Reflect.get(target, key, receiver)
                // 深度监听(惰性)
                if (isObject(result)) {
                    return reactive(result);
                }
                return result;
            },
    
            set(target, key, value, receiver) {
                console.log(`设置对象属性${key}值`)
    
                // 首先先获取旧值
                const oldValue = Reflect.get(target, key, reactive)
    
                let result = Reflect.set(target, key, value, receiver);
                
                if (result && oldValue !== value) {
                    // 更新操作 ...
                }
                return result
            },
    
            deleteProperty(target, key) {
                console.log(`删除对象属性${key}值`)
    
                // 先判断是否有key
                const hadKey = hasOwn(target, key)
                const result = Reflect.deleteProperty(target, key)
    
                if (hadKey && result) {
                    // 更新操作 ...
                }
    
                return result
            },
            
            // 其他方法
            // ...
        }
        return new Proxy(target, handler)
    }
    
    const obj = { a: { b: { c: 6 } } };
    const proxy = reactive(obj);
    
    proxy.a.b.c = 77;
    
    // 获取对象属性a值
    // 获取对象属性b值
    // 设置对象属性c值 77

    至此,引用类型的对象我们已经可以把它转化成响应式对象了,Proxy对象只能代理引用类型的对象,对于基本数据类型如何实现响应式呢?

    vue的解决方法是把基本数据类型变成一个对象:这个对象只有一个value属性,value属性的值就等于这个基本数据类型的值。然后,就可以用reative方法将这个对象,变成响应式的Proxy对象。

    实际上就是: ref(0) --> reactive( { value:0 })

    ref

    作用:把基本类型的数据变为响应式数据。

    参数:

    1.基本数据类型

    2.引用类型

    3.DOM的ref属性值

    ref 实现 Vue3 源码

    export function ref(value?: unknown) {
      return createRef(value, false)
    }
    
    function createRef(rawValue: unknown, shallow: boolean) {
      if (isRef(rawValue)) {
        return rawValue
      }
      return new RefImpl(rawValue, shallow)
    }
    
    class RefImpl<T> {
      private _value: T
      private _rawValue: T
    
      public dep?: Dep = undefined
      public readonly __v_isRef = true
    
      constructor(value: T, public readonly __v_isShallow: boolean) {
        this._rawValue = __v_isShallow ? value : toRaw(value)
        this._value = __v_isShallow ? value : toReactive(value)
      }
    
      get value() {
        trackRefValue(this)
        return this._value
      }
    
      set value(newVal) {
        const useDirectValue =
          this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
        newVal = useDirectValue ? newVal : toRaw(newVal)
        if (hasChanged(newVal, this._rawValue)) {
          this._rawValue = newVal
          this._value = useDirectValue ? newVal : toReactive(newVal)
          triggerRefValue(this, newVal)
        }
      }
    }

    大体思路就是,调用ref函数时会new 一个类,这个类监听了value属性的 get 和 set ,实现了在get中收集依赖,在set中触发依赖,而如果需要对传入参数深层监听的话,就会调用我们上面提到的reactive方法。

    即:

    ref(0); // 通过监听对象(类)的value属性实现响应式
    ref({a: 6}); // 调用reactive方法对对象进行深度监听

    根据上面的思路我们可以自己来简单实现下:

    // 自定义ref
    function ref(target) {
      const result = { // 这里在源码中体现为一个类 RefImpl
        _value: reactive(target), // target传给reactive方法做响应式处理,如果是对象的话就变成响应式
        get value () {
          return this._value
        },
        set value (val) {
          this._value = val
          console.log('set value 数据已更新, 去更新界面')
        }
      }
     
      return result
    }
     
    // 测试
    const ref = ref(9);
    ref.value = 6;
    
    const ref = ref({a: 4});
    ref.value.a = 6;

    ref 方法包装的数据,需要使用.value 来访问,但在模板中不需要,Vue解析时会自动添加。

    总结

    (学习视频分享:web前端开发编程基础视频

    以上就是原理详解:Vue3中reactive和ref的区别的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除

    前端(VUE)零基础到就业课程:点击学习

    清晰的学习路线+老师随时辅导答疑

    快捷开发Web应用及小程序:点击使用

    支持亿级表,高并发,自动生成可视化后台。

    专题推荐:reactive ref Vue vue3
    上一篇:vue不能用index做为唯一标识的原因浅析 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • ❤️‍🔥共22门课程,总价3725元,会员免费学• ❤️‍🔥接口自动化测试不想写代码?• 【整理总结】详解Vue3的11个知识点• 一文详解Vue3项目中怎么引入 SVG 图标• 聊聊怎么用Vue3构建Web Components• 9个vue3开发技巧,提升效率帮助你早点下班!• 聊聊vue3中的name属性,看看怎么使用!• 一文深入剖析Vue3中的响应式机制
    1/1

    PHP中文网