Vue3响应式源码@vue/reactivity深入解析之reactive(1)
🌹 vue3 的响应式API总体分4类。
基础API
即reactive相关的API。
export {
reactive,
readonly,
isReactive,
isReadonly,
isProxy,
shallowReactive,
shallowReadonly,
markRaw,
toRaw,
ReactiveFlags,
DeepReadonly,
ShallowReactive,
UnwrapNestedRefs
} from './reactive'
Ref相关API
export {
ref,
shallowRef,
isRef,
toRef,
toRefs,
unref,
proxyRefs,
customRef,
triggerRef,
Ref,
ToRef,
ToRefs,
UnwrapRef,
ShallowUnwrapRef,
RefUnwrapBailTypes
} from './ref'
Computed 与 watch相关API
export {
computed,
ComputedRef,
WritableComputedRef,
WritableComputedOptions,
ComputedGetter,
ComputedSetter
} from './computed'
Effect 作用域 相关API
export {
effect,
stop,
trigger,
track,
enableTracking,
pauseTracking,
resetTracking,
ITERATE_KEY,
ReactiveEffect,
ReactiveEffectRunner,
ReactiveEffectOptions,
EffectScheduler,
DebuggerOptions,
DebuggerEvent,
DebuggerEventExtraInfo
} from './effect'
export {
effectScope,
EffectScope,
getCurrentScope,
onScopeDispose
} from './effectScope'
当然有些API并没有完全提供给用户使用。
还有部分API在runtime-core包中。
从reactive讲起
返回对象的响应式副本
// 通过reactive(target)即创建一个响应式对象副本obj
//Vue 中响应式状态的基本用例是我们可以在渲染期间使用它。因为依赖跟踪的关系,当响应式状态改变时视图会自动更新。
const obj = reactive({ count: 0 })
响应式转换是“深层”的——它影响所有嵌套 property。
在基于 ES2015 Proxy 的实现中,返回的 proxy 是不等于原始对象的。建议只使用响应式 proxy,避免依赖原始对象。
类型声明:
function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
这就是 Vue3 响应性系统的本质。当从组件中的 data()
返回一个对象时,它在内部交由 reactive()
使其成为响应式对象。
模板会被编译成能够使用这些响应式 property 的渲染函数。
reactive的声明与源码
//接受object对象,例如 {}、[]等都可以
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
reactive函数的本质是创建一个Proxy实例,即const p = new Proxy(target, handler)
,可以侦听到用户 get 或者 set 的动作 。
但是,vue底层却做了很多处理,例如缓存,是否只读等判断。
reactiveMap
export const reactiveMap = new WeakMap();
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。
WeakMap
结构与Map
结构类似, 但是WeakMap
只接受对象作为键名(null
除外),不接受其他类型的值作为键名。其次,
WeakMap
的键名所指向的对象,不计入垃圾回收机制。只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。
也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null, 2)
// TypeError: Invalid value used as weak map key
const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"
注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
const wm = new WeakMap();
let key = {};
let obj = {foo: 1};
wm.set(key, obj);
obj = null;
wm.get(key)
// Object {foo: 1}
上面代码中,键值obj
是正常引用。所以,即使在 WeakMap 外部消除了obj
的引用,WeakMap 内部的引用依然存在。
⚡ 因此,WeakMap
只有四个方法可用:get()
、set()
、has()
、delete()
。
完整版的createReactiveObject
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
//异常处理,可以忽略
//reactive 的参数必须是object类型,否则返回的还是原始对象,没有任何响应式,如果是开发环境,抛出异常
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made 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
}
// 1、 核心代码1:已经缓存了,直接返回existingProxy
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 只有值类型的才可以观测到
// only a whitelist of value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
//2、 核心代码2: 创建响应式对象
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
//3、 核心代码3: 缓存起来
proxyMap.set(target, proxy)
return proxy
}
精简版的createReactiveObject
function createReactiveObject(target, proxyMap, baseHandlers) {
// 缓存proxy
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
const proxy = new Proxy(target, baseHandlers);
// 把创建好的 proxy 给缓存起来,
proxyMap.set(target, proxy);
return proxy;
}
之前我们提到handlers总共可以代理的方法总共有13个,其中最基本的是get或set,那vue是如何处理的呢?
以下代码已经精简:
const get = createGetter();
const set = createSetter();
export const mutableHandlers = {
get,
set,
};
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
const isExistInReactiveMap = () =>
key === ReactiveFlags.RAW && receiver === reactiveMap.get(target);
const isExistInReadonlyMap = () =>
key === ReactiveFlags.RAW && receiver === readonlyMap.get(target);
const isExistInShallowReadonlyMap = () =>
key === ReactiveFlags.RAW && receiver === shallowReadonlyMap.get(target);
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly;
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly;
} else if (
isExistInReactiveMap() ||
isExistInReadonlyMap() ||
isExistInShallowReadonlyMap()
) {
return target;
}
const res = Reflect.get(target, key, receiver);
//如果是只读的,不收集依赖
if (!isReadonly) {
// 在触发 get 的时候进行依赖收集
track(target, "get", key);
}
if (shallow) {
return res;
}
if (isObject(res)) {
// 把内部所有的是 object 的值都用 reactive 包裹,变成响应式对象
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
}
function createSetter() {
return function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
// 在触发 set 的时候进行触发依赖
trigger(target, "get", key);
return result;
};
}
export function track(target, type, key) {
if (!isTracking()) {
return;
}
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = createDep();
depsMap.set(key, dep);
}
trackEffects(dep);
}
export function trigger(target, type, key) {
// 存放收集的依赖
let deps: Array<any> = [];
const depsMap = targetMap.get(target);
if (!depsMap) return;
// get 类型只需要取出来就可以
const dep = depsMap.get(key);
// 最后收集到 deps 内
deps.push(dep);
const effects: Array<any> = [];
deps.forEach((dep) => {
// 这里解构 dep 得到的是 dep 内部存储的 effect
effects.push(...dep);
});
// 这里的目的是只有一个 dep ,这个dep 里面包含所有的 effect
// 这里的目前应该是为了 triggerEffects 这个函数的复用
triggerEffects(createDep(effects));
}
export const createDep = (effects?: ReactiveEffect[]): Dep => {
const dep = new Set<ReactiveEffect>(effects) as Dep
dep.w = 0
dep.n = 0
return dep
}
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
for (const effect of isArray(dep) ? dep : [...dep]) {
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
}