ホームページ  >  記事  >  ウェブフロントエンド  >  原理の詳しい説明:Vue3におけるreactiveとrefの違い

原理の詳しい説明:Vue3におけるreactiveとrefの違い

青灯夜游
青灯夜游転載
2022-09-27 20:32:082452ブラウズ

この記事は、vue3 の最も重要な API (ref と reactive) を理解するのに役立ちます。どれを使用すればよいかまだ迷っていますか? Vue3 をうまく使いたい場合は、見てみましょう

原理の詳しい説明:Vue3におけるreactiveとrefの違い

vue2 の応答性は、Object.defineProperty メソッドを通じてオブジェクトの gettersetter をハイジャックし、# で依存関係を収集することです。 ##getter および # 依存関係は ##setter でトリガーされますが、このメソッドにはいくつかの欠点があります。プロパティが多すぎるか、ネスト レベルが深すぎます。 パフォーマンスに影響します

  • オブジェクトを監視できません新しい属性と削除された属性。オブジェクト自体に存在する属性のみを監視するため、

    $set
  • $delete

    配列を監視する場合は、配列要素の増加または減少は監視できません。監視できるのは添え字を介してアクセスできる要素のみです。配列内の既存のプロパティの場合、Object.defineProperty

    を使用して元の要素を走査するためです。監視配列はパフォーマンスを消費しすぎます
  • 、vue は
  • Object.defineProperty

    を使用して配列を監視することを放棄しました、および 配列プロトタイプ メソッドをオーバーライドするメソッドが配列データの操作を監視するために使用されます、そして$set メソッドと splice メソッドは配列の更新に使用され、$setsplice はオーバーライドされた配列メソッドを呼び出します。 #[関連する推奨事項: vuejs ビデオ チュートリアル ]

  • vue3 レスポンシブ実装

#プロキシ オブジェクト

Object.defineProperty の欠点に対応して、新しいオブジェクト - Proxy (オブジェクト プロキシ) が ES6 に導入されました。プロキシ オブジェクト:

usedオブジェクトのプロキシを作成します。主にオブジェクトの一部のデフォルト動作を変更するために使用されます。プロキシは、ターゲット オブジェクトの前に「インターセプト」層を設定するものとして理解できます。オブジェクトへの外部アクセスは、最初にこのインターセプト層を通過する必要がありますこれにより、外部アクセスをフィルタリングして書き換えるメカニズムが提供されます。基本的な構文は次のとおりです。
/*
 * target: 目标对象
 * handler: 配置对象,用来定义拦截的行为
 * proxy: Proxy构造器的实例
 */
 var proxy = new Proxy(target,handler)
Intercept get, valueoperation

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

インターセプトできる操作は次のとおりです:

Function

操作

set演算子に##deletePropertyObject.getPrototypeOf( )getPrototypeOfObject.getPrototypeOf()setPrototypeOfObject.setPrototypeOf()#Object.isExtensible()preventExtensionsObject.preventExtensions()
get 値の読み取り
値を書き込みます
があります
#isExtensible
#getOwnPropertyDescriptor Object.getOwnPropertyDescriptor()
defineProperty Object.defineProperty
ownKeys Object.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におけるreactiveとrefの違いの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。