>首页> IT >

焦点精选!原理详解:Vue3中reactive和ref的区别

时间:2022-09-27 20:58:48       来源:转载
一文带你看懂vue3中最重要的API——ref和reactive,还在纠结用哪个么,想把Vue3用好快来看

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


(资料图)

由于是遍历递归监听属性,当属性过多或嵌套层级过深时会影响性能

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

如果监听数组的话,无法监听数组元素的增减,只能监听通过下标可以访问到的数组中已有的属性,由于使用Object.defineProperty遍历监听数组原有元素过于消耗性能,vue放弃使用Object.defineProperty监听数组,而采用了重写数组原型方法的方式来监听对数组数据的操作,并用$setsplice方法来更新数组,$setsplice会调用重写后的数组方法。

【相关推荐: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 // 35proxy.name // 35proxy.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中的哪些问题,总结一下:

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.applyObject.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{  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方法对对象进行深度监听
登录后复制

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

// 自定义reffunction 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中文网其它相关文章!

关键词: 引用类型 基本数据类型 基本类型