前言
vue3使用Proxy实现了响应式,这件众所周知的事,会让人下意识的认为reactive、ref等响应式API都使用了Proxy进行了代理。读了源码才发现,ref并没有使用Proxy实现响应式,而是使用了Class的getter和setter进行了数据拦截
1. Ref
源码文件位置:packages\reactivity\src\ref.ts
ref函数很简单,就是直接调用createRef
js
export function ref(value?: unknown) {
return createRef(value);
}
2. createRef
createRef:1. 先使用isRef检查传入的值是否是已经被代理过的,是的话直接返回;2. 如果是没有被代理过,则实例化RefImpl类
js
function createRef(rawValue: unknown, shallow = false) {
if (isRef(rawValue)) {
// 已经被代理过,直接返回值
return rawValue
}
//实例化RefImpl
return new RefImpl(rawValue, shallow)
}
isRef:判断目标对象的__v_isRef属性值是否为true
js
export function isRef(r: any): r is Ref {
return Boolean(r && r.__v_isRef === true)
}
isRef只是判断逻辑,接着看此次的重点RefImpl类
3. RefImpl
1. 私有属性
_value:访问属性时返回的值
__v_isRef:标识当前对象以被Ref处理过
_rawValue:传入的原始值
_shallow:是否是ref的浅层作用形式
2. 构造函数
构造函数中先判断_shallow是否为true,为true直接返回传入的原始值,否则调用convert函数
js
constructor(private _rawValue: T, private readonly _shallow = false) {
this._value = _shallow ? _rawValue : convert(_rawValue)
}
3. get、set
在get方法中调用track收集依赖;在set方法中当新值与旧值不等时,调用trigger触发更新。track与trigger不清楚的,可参考:更适合入门的vue3响应式原理解析(纯干货分享)
js
class RefImpl<T> {
//访问属性时返回的值
private _value: T
//标识当前对象以被Ref处理过
public readonly __v_isRef = true
//_rawValue:传入的原始值
//_shallow:是否是ref的浅层作用形式
constructor(private _rawValue: T, private readonly _shallow = false) {
this._value = _shallow ? _rawValue : convert(_rawValue)
}
get value() {
//收集依赖
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newVal) {
//判断值是否相等
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
//触发更新
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}
从源码中,可以看出ref的定义与reactive不同。reactive是通过Proxy进行数据拦截,而ref则是通过类的getter和setter进行数据拦截
也解决了另一个疑惑点,为什么ref需要.value取值。因为取值函数get、存值函数set的定义的属性名称为value。
4. convert
convert:判断传入的值是否为对象,如果是对象交给reactive处理,否则返回原值
js
const convert = <T extends unknown>(val: T): T =>
isObject(val) ? reactive(val) : val
说明ref也是可以处理引用数据类型的,因为内部会进行判断,如果是对象,则使用reactive处理。
ref可以定义原始数据类型,也可以定义引用数据类型,那么reactive可以定义原始数据类型吗?答案是不可以,因为Proxy只能代理对象
js
//Uncaught TypeError: Cannot create proxy with a non-object as target or handler
const proxy = new Proxy(1, {
get: function (target, key) {
return target[key];
},
});
四、总结
-
reactive通过Proxy进行数据拦截;ref通过class的Getter/Setter进行数据拦截
-
reactive只能定义引用数据类型;ref可以定义原始数据类型,也能定义引用数据类型
因为ref取值需要通过.value,会多一层嵌套,因此在开发中,常使用reactive定义引用数据类型,使用ref定义原始数据类型