vue3的reactive和ref区别
vue3的数据双向绑定,大家都明白是proxy数据代理,但是在定义响应式数据的时候,有ref和reactive两种方式,如果判断该使用什么方式,是大家一直不很清楚地问题。
首先,明白一点,Vue3 的 reactive 和 ref 是借助了vue3的Proxy代理来实现的。
reactive方式
参数: 对象或数组
作用:创建原始数据对象的响应式副本,即将「引用数据类型」的数据转换为「响应式」数据,这个操作就是实现页面的响应式
reactive的实现:
// 首先,判断是否为对象
const isObject =( val )=> {
return val !== null && typeof val === 'object';
}
// 判断key是否存在
const hasOwn = (value, key) => {
return Object.prototype.hasOwnProperty.call(value, key);
}
export function reactive(value) {
// 首先先判断是否为对象
if (!isObject(value)) return target;
const handler = {
get(value, key, receiver) {
console.log(`获取对象属性${key}值`)
// 收集依赖 ...
const result = Reflect.get(value, key, receiver)
// 深度监听(惰性)
if (isObject(result)) {
return reactive(result);
}
return result;
},
set(value, key, value, receiver) {
console.log(`设置对象属性${key}值`)
// 首先先获取旧值
const oldValue = Reflect.get(value, key, reactive)
let res = Reflect.set(value, key, value, receiver);
if (res && oldValue !== value) {
// 更新操作 ...
}
return res
},
deleteProperty(value, key) {
console.log(`删除对象属性${key}值`)
// 先判断是否有key
const hadKey = hasOwn(value, key)
const res = Reflect.deleteProperty(value, key)
if (hadKey && res) {
// 更新操作 ...
}
return res
},
// 其他方法
// ...
}
return new Proxy(value, handler)
}
const obj = { a: { b: { c: 1 } } };
const proxy = reactive(obj);
proxy.a.b.c = 100;
// 获取对象属性a值
// 获取a值对象属性b值
// 设置b值对象属性c值 100
至此,引用数据类型的对象我们被我们它转化成响应式对象了。需要注意的是,Proxy对象只能代理引用数据类型的对象。
对于基本数据类型如何实现响应式呢?
vue源码给出的的解决方案是把基本数据类型套一个对象外壳变成一个对象:这个对象只有一个value属性,value属性的值就等于这个基本数据类型的值。然后,就可以用reactive方法将这个对象,变成响应式的Proxy对象。
实际上就是: ref(0) 等同于 reactive( { value:0 })
ref方式
参数:基本数据类型/引用类型/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)
}
const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
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)
}
}
}
在vue3中调用ref函数时会js会先new 一个class,这个class监听了value属性的 get 和 set方法 ,实现了在get中收集依赖,在set中触发依赖的双向绑定过程,而如果需要对传入参数深层监听的话,就需要调用我们上面提到的reactive方法。
即:
const a = ref(0); // 通过监听对象a的value属性实现响应式
const b = ref({a: 6}); // 调用reactive方法对对象进行深度监听,b.value时获取的则是这个响应式对象
简单实现下:
// 自定义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 方法包装的数据,zai方法中访问需要使用.value 属性,但在模板展示的时候不需要,Vue解析页面的时候会自动添加。
总结
- reactive 将引用数据类型值变为响应式,使用Proxy实现。
- ref 可将基本数据类型和引用数据类型都变成响应式,通过套壳成对象,监听对象class的value属性的get和set方法实现,但是当传入的值为引用数据类型时实际上内部还是使用reactive 方法进行的处理。
- 推荐基本数据类型使用ref,引用数据类型使用 reactive。