一文带你看懂vue3中最重要的api——ref和reactive,还在纠结用哪个么,想把vue3用好快来看
vue2的响应式是通过object.defineproperty 方法,劫持对象的getter和setter,在getter中收集依赖,在setter中触发依赖,但是这种方式存在一些缺点:
由于是遍历递归监听属性,当属性过多或嵌套层级过深时会影响性能
无法监听对象新增的属性和删除属性,只能监听对象本身存在的属性,所以设计了$set和$delete
如果监听数组的话,无法监听数组元素的增减,只能监听通过下标可以访问到的数组中已有的属性,由于使用object.defineproperty遍历监听数组原有元素过于消耗性能,vue放弃使用object.defineproperty监听数组,而采用了重写数组原型方法的方式来监听对数组数据的操作,并用$set和splice 方法来更新数组,$set和splice会调用重写后的数组方法。
【相关推荐: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 写入一个值
has in操作符
deleteproperty object.getprototypeof()
getprototypeof object.getprototypeof()
setprototypeof object.setprototypeof()
isextensible object.isextensible()
preventextensions object.preventextensions()
getownpropertydescriptor object.getownpropertydescriptor()
defineproperty object.defineproperty
ownkeys object.keys() object.getownpropertynames()和object.getownpropertysymbols()
apply 调用一个函数
construct new一个函数
那么使用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 obj和delete obj[name],而reflect.has(obj, name)和reflect.deleteproperty(obj, name)让它们变成了函数行为。
reflect对象的方法与proxy对象的方法一一对应,只要是proxy对象的方法,就能在reflect对象上找到对应的方法。这就让proxy对象可以方便地调用对应的reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管proxy怎么修改默认行为,你总可以在reflect上获取默认行为。
vue3的reative和refvue3 的 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方法对对象进行深度监听
根据上面的思路我们可以自己来简单实现下:
// 自定义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属性的get和set实现,但是当传入的值为引用类型时实际上内部还是使用reactive方法进行的处理推荐基本类型使用ref,引用类型使用 reactive(学习视频分享:web前端开发、编程基础视频)
以上就是原理详解:vue3中reactive和ref的区别的详细内容。