>  기사  >  웹 프론트엔드  >  원리에 대한 자세한 설명: Vue3의 반응성과 참조의 차이점

원리에 대한 자세한 설명: Vue3의 반응성과 참조의 차이점

青灯夜游
青灯夜游앞으로
2022-09-27 20:32:082452검색

이 글은 vue3에서 가장 중요한 API인 ref와 반응형을 이해하는 데 도움이 될 것입니다. 아직도 어떤 것을 사용해야 할지 헷갈리시나요? Vue3를 잘 활용하고 싶다면 곧 vue2의 반응성을 살펴보세요.

Object.defineProperty

메소드를 통해 객체의 gettersetter를 하이재킹하고 getter에서 종속성을 수집하고 에서 종속성을 트리거합니다. setter이지만 이 방법에는 몇 가지 단점이 있습니다. 원리에 대한 자세한 설명: Vue3의 반응성과 참조의 차이점

  • 속성을 순회하고 재귀적으로 모니터링하기 때문에 속성이 너무 많거나 중첩 수준이 너무 깊어 성능에 영향을 미칩니다

  • gettersetter,在getter中收集依赖,在setter中触发依赖,但是这种方式存在一些缺点:
    • 由于是遍历递归监听属性,当属性过多或嵌套层级过深时会影响性能

    • 无法监听对象新增的属性和删除属性,只能监听对象本身存在的属性,所以设计了$set$delete

    • 如果监听数组的话,无法监听数组元素的增减,只能监听通过下标可以访问到的数组中已有的属性,由于使用Object.defineProperty遍历监听数组原有元素过于消耗性能,vue放弃使用Object.defineProperty监听数组,而采用了重写数组原型方法的方式来监听对数组数据的操作,并用$setsplice 方法来更新数组,$setsplice객체의 새 속성과 삭제된 속성은 모니터링할 수 없습니다

      객체 자체에 존재하는 속성만 모니터링할 수 있으므로 $를 설계했습니다. set$delete
  • 배열을 모니터링하면 배열 요소의 증가 또는 감소를 모니터링할 수 없습니다. 배열의 기존 속성만 모니터링할 수 있습니다. Object.defineProperty 순회 모니터링을 통해 액세스할 수 있습니다. Vue는 모니터링을 위해 Object.defineProperty 사용을 포기했습니다. 배열 데이터의 동작을 모니터링하기 위해 배열 프로토타입 메서드를 재정의하는 방법을 채택하고 $setsplice 메서드를 사용하여 배열을 업데이트했습니다. >$set 및 splice는 재정의된 배열 메서드를 호출합니다.

    【관련 추천: vuejs 동영상 튜토리얼

    vue3 반응형 구현

    프록시 객체

    새로운 객체인 Object.defineProperty의 단점을 겨냥 - ES6에서 프록시가 도입되었습니다(객체 프록시 )

    프록시 개체:

    는 개체에 대한 프록시를 만드는 데 사용되며 주로 개체의 일부 기본 동작을 변경하는 데 사용됩니다. 프록시는 대상 개체 앞에 "가로채기" 계층을 설정하는 것으로 이해될 수 있습니다. 모든 액세스는 먼저 이 차단 계층을 통과해야 하므로 외부 액세스를 필터링하고 다시 작성할 수 있는 메커니즘을 제공해야 합니다. 기본 구문은 다음과 같습니다.
    /*
     * target: 目标对象
     * handler: 配置对象,用来定义拦截的行为
     * proxy: Proxy构造器的实例
     */
     var proxy = new Proxy(target,handler)
    Intercept get, value 연산
    var proxy = new Proxy({}, {
      get: function(target, propKey) {
        return 35;
      }
    });
    
    proxy.time // 35
    proxy.name // 35
    proxy.title // 35
    Functionoperationget값 읽기set 값 쓰기hasin 연산자deletePropertyObject.getPrototypeOf()getPrototypeOfObject.getPrototypeOf ()setPrototypeOfObject.setPrototypeOf ( )isExtensibleObject.isExtensible()preventExtensionsObject.preventExtensions()getOwnPropertyDescriptorObject.getOwnProperty 설명자() definePropertyObject.definePropertyownKeysObject.keys() Object.getOwnPropertyNames() 및 Object.getOwnPropertySymbols()apply함수 호출construct
    중단할 수 있는 연산은 다음과 같습니다.
    🎜새 함수🎜🎜 🎜 🎜

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

    • Proxy是对整个对象的代理,而Object.defineProperty只能代理某个属性。
    • 对象上新增属性,Proxy可以监听到,Object.defineProperty不能。
    • 数组新增修改,Proxy可以监听到,Object.defineProperty不能。
    • 若对象内部属性要全部递归代理,Proxy可以只在调用的时候递归,而Object.definePropery需要一次完成所有递归,Proxy相对更灵活,提高性能。

    递归代理

    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 的作用和意义

    • 规范语言内部方法的所属对象,不全都堆放在Object对象或Function等对象的原型上。如
    Function.prototype.apply
    Object.defineProperty
    • 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

    • Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

    • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

    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解析时会自动添加。

    总结

    • reactive 将引用类型值变为响应式,使用Proxy实现
    • ref 可将基本类型和引用类型都变成响应式,通过监听类的value属性的getset实现,但是当传入的值为引用类型时实际上内部还是使用reactive方法进行的处理
    • 推荐基本类型使用ref,引用类型使用 reactive

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

위 내용은 원리에 대한 자세한 설명: Vue3의 반응성과 참조의 차이점의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제