之前一直使用vue开发项目,没有去研究底层源码,俗话说要站在巨人的肩膀上学习,那现在抽出时间去学习研究一下,写文章只是记录我学习的过程,如有错误欢迎批评指正。vue为3.5版本
废话不多说,直接开怼!!!
reactive
vue3采用组合式API,reactive和ref是响应式的基础,那就从这俩入手,先来看下定义reactive的源码:
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
从以上代码可以看出,reactive只接受类型为object的参数,如果target为只读则直接返回。再来看看createReactiveObject函数
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
if (!isObject(target)) {
if (__DEV__) {
warn(
`value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(
target,
)}`,
)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// only specific value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
}
从上面代码我们可以得到以下几点:
已经代理过的目标对象和只读的代理对象直接返回
通过getTargetType函数判断target类型:INVALID无效、COMMON(object、array)、COLLECTION(map、set、weakMap、weakSet)
proxyMap存储已经代理过的对象避免重复代理,影响性能
说一下这个proxyMap,它使用了WeakMap弱引用,当没有任何引用时,将会被垃圾回收。在这里看到Proxy本尊,也就是它让数据“活”起来了。主要看下Proxy第二个参数,我们研究一下在实际项目中使用最多的object和array,就直接看baseHandlers,先来看下get拦截函数(有删减)
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false,
protected readonly _isShallow = false,
) {}
get(target: Target, key: string | symbol, receiver: object): any {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
}
.
.
.
const res = Reflect.get(
target,
key,
// if this is a proxy wrapping a ref, return methods using the raw ref
// as receiver so that we don't have to call `toRaw` on the ref in all
// its class methods
isRef(target) ? target : receiver,
)
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
代理对象访问属性时,会走get方法,可自定义一些属性,比如访问target[ReactiveFlags.IS_REACTIVE],就会返回(!isReadonly)的值。通过Reflect.get()获取值,如果是对象且不是只读,就会继续调用reactive(),达到深度响应。最后这个track()在这里就先不讲,暂时记得它是建立依赖,把这个响应对象放在一个桶里(dep)
再来看看baseHandlers中set拦截函数(有删减)
class MutableReactiveHandler extends BaseReactiveHandler {
constructor(isShallow = false) {
super(false, isShallow)
}
set(
target: Record<string | symbol, unknown>,
key: string | symbol,
value: unknown,
receiver: object,
): boolean {
let oldValue = target[key]
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(
target,
key,
value,
isRef(target) ? target : receiver,
)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
来先看看注释的一行,翻译过来就是如果目标是原型链上的东西,不要触发,为什么要做这样的判断呢?因为如果对象自身不存在该属性,就会获取对象原型,并调用原型的[[Get]]方法得到最终结果。比如创建两个响应式对象obj1 = reactive({})和obj2 = reactive({a: 1}),将obj2设置为obj1的原型,当设置obj1.a = 2时,如果不加判断,副作用函数就会执行两次,造成不必要的更新,两次更新是由于set拦截函数触发两次导致,只要屏蔽一次更新就可以。
当设置obj1.a的值时会执行obj1代理对象的set拦截函数
// boj1的 set拦截函数
set(target,key,value,receiver){
// target是原始对象{}
// receiver是代理对象obj1
}
由于obj1上不存在a属性,所以会取得obj1的原型obj2,并执行obj2代理对象的set拦截函数
// boj2的 set拦截函数
set(target,key,value,receiver){
// target是原始对象{a:1}
// receiver是代理对象仍是obj1
}
通过toRaw()方法获取原始值,这样就可以屏蔽由原型引起的更新了。接下来就是根据变量hadKey来判断是否是新增还是更新,更新的话则会跟老值判断是否相等,不一样则触发更新,trigger()同样在这里不做讲解,记住它是触发更新的,从桶里拿出来更新。
ref
由于Proxy的代理目标必须是非原始值,所以必须使用一个非原始值去“包裹”原始值。来看下ref源码(有删减)
class RefImpl<T = any> {
_value: T
private _rawValue: T
dep: Dep = new Dep()
public readonly [ReactiveFlags.IS_REF] = true
public readonly [ReactiveFlags.IS_SHALLOW]: boolean = false
constructor(value: T, isShallow: boolean) {
this._rawValue = isShallow ? value : toRaw(value)
this._value = isShallow ? value : toReactive(value)
this[ReactiveFlags.IS_SHALLOW] = isShallow
}
get value() {
this.dep.track()
return this._value
}
set value(newValue) {
const oldValue = this._rawValue
const useDirectValue =
this[ReactiveFlags.IS_SHALLOW] ||
isShallow(newValue) ||
isReadonly(newValue)
newValue = useDirectValue ? newValue : toRaw(newValue)
if (hasChanged(newValue, oldValue)) {
this._rawValue = newValue
this._value = useDirectValue ? newValue : toReactive(newValue)
this.dep.trigger()
}
}
}
这代码看起来好理解的多,通过toReactive()方法如果是对象则调用reactive进行响应式代理,原始值直接返回。 在 Class 内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。代码逻辑已经很清楚了,就不多讲了。这里涉及到的Dep不在这里讲解,会专门出一篇文章。
第一次写文采不行,如果有错误的欢迎指出。大佬们,轻点喷...