以 ref/reactive 为切入点探讨响应式及依赖搜集
预备知识:
vue2的响应式是通过发布订阅模式完成的,在get里面搜集依赖,在set里面触发依赖;vue3的大体模式也差不多vue2的依赖搜集是dep+watcher作双向依赖搜集完成的;vue3是建立全局的WeakMap结构完成,以劫持监听的obj为key,value是一个Map类型,以属性名为key,value是一个Set类型,Set里面存放的是effect函数,effect就是副作用函数,用来更新
reactive
// packages\reactivity\src\reactive.ts
export function reactive(target: object) {
// 调用 createReactiveObject 方法创建 reactive
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
);
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
// WeakMap 是类似 map 的数据结构,只能使用对象作为键,不能使用基本数据类型。
// 并且是弱引用,当只有 WeakMap 引用时,它会被垃圾回收,WeakMap 中对应的条目也会被自动删除
proxyMap: WeakMap<Target, any>
) {
// 看缓存有没有,有直接返回
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
// 这里会判断类型,对象和数组会返回1
const targetType = getTargetType(target);
if (targetType === 0) {
return target;
}
// 用 Proxy 做数据劫持,会传入 baseHandlers,即上面的 mutableHandlers
const proxy = new Proxy(
target,
targetType === 2 ? collectionHandlers : baseHandlers
);
// 缓存
proxyMap.set(target, proxy);
return proxy;
}
// packages\reactivity\src\baseHandlers.ts
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys,
};
const get = /*#__PURE__*/ createGetter(); // 这里先看 get
const set = /*#__PURE__*/ createSetter();
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// ...省略部分代码
track(target, TrackOpTypes.GET /** get */, key);
// 扩展 shallowReactive api 做浅层响应,不会循环处理对象
if (shallow) {
return res;
}
if (isObject(res)) {
// 对对象要循环处理
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
}
// packages\reactivity\src\effect.ts
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
// targetMap 是 WeakMap 结构,以劫持监听的 obj 为 key,value 是一个 Map 类型
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
// depsMap 是 Map 结构,以属性名为 key,value 是一个 Set 类型
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = createDep()));
}
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined;
trackEffects(dep, eventInfo);
}
}
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
/** 做双向绑定
* activeEffect 是当前运行的 ReactiveEffect 实例
* 类似 vue2 中 watcher 和 dep 双向搜集依赖
* 在这里打印 activeEffect.fn
* 控制台显示 componentUpdateFn 函数,正是渲染相关的
* if (!instance.isMounted) {
let vnodeHook;
const { el, props } = initialVNode;
const { bm, m, parent } = instance;
const isAsyncWrapperVNode = isAsyncWrap…
* 比如
const obj = reactive({ name: '张三' });
const com = computed(() => obj.name);
* 在页面渲染获取 com 时会触发 obj 的 get,这时候 activeEffect 就是 com
* activeEffect.fn 就是 () => obj.name
*/
// todo 这里猜测 ReactiveEffect 有渲染、计算和监听类型
dep.add(activeEffect!);
activeEffect!.deps.push(dep);
}
回到 set 方法处理
// packages\reactivity\src\baseHandlers.ts
const set = /*#__PURE__*/ createSetter();
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key];
// 判断是增加属性/值,还是更新
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
// 更新对应的值
const result = Reflect.set(target, key, value, receiver);
// 避免原型链属性修改触发
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;
};
}
// packages\reactivity\src\effect.ts
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
// 从之前搜集的全局依赖获取
const depsMap = targetMap.get(target);
let deps: (Dep | undefined)[] = [];
// 下面就是根据不同情况,获取要触发更新的 dep
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
deps.push(depsMap.get(key));
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY/** '' */));
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY/** '' */));
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
deps.push(depsMap.get("length"));
}
break;
case TriggerOpTypes.SET:
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY/** '' */));
}
break;
}
if (deps.length === 1) {
if (deps[0]) {
triggerEffects(deps[0]);
}
} else {
const effects: ReactiveEffect[] = [];
for (const dep of deps) {
if (dep) {
effects.push(...dep);
}
}
// createDep 返回的是一个 Set 集合,做过滤
triggerEffects(createDep(effects));
}
}
export function triggerEffects(
dep: Dep | ReactiveEffect[]
) {
// spread into array for stabilization
const effects = isArray(dep) ? dep : [...dep]
// 先处理计算属性api
for (const effect of effects) {
if (effect.computed) {
triggerEffect(effect)
}
}
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect)
}
}
}
function triggerEffect(
effect: ReactiveEffect,
) {
// 执行 effect.run(),触发更新
// run 就是 执行 fn(),即上面分享的 计算属性内容/渲染函数
if (effect !== activeEffect || effect.allowRecurse) {
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
ref
// packages\reactivity\src\ref.ts
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)
/**
* toReactive 判断是否是对象,如果是对象,则使用 reactive,否则直接返回
* 即对象类型的 ref 会使用 reactive,非对象类型用 class 处理
* isObject(value) ? reactive(value) : value
*/
this._value = __v_isShallow ? value : toReactive(value)
}
// 对 value 分别做 get/set 方法处理,所以使用时要用 .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)
}
}
}
export function trackRefValue(ref: RefBase<any>) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref)
// trackEffects 上面有讲,做了双向依赖收集
trackEffects(ref.dep || (ref.dep = createDep()))
}
}
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
ref = toRaw(ref)
if (ref.dep) {
// triggerEffects 上面有讲,触发对应的依赖
triggerEffects(ref.dep)
}
}
reactive 与 ref 区别
- 处理的数据类型不同
ref定义的变量可以做直接赋值全部替换,类似多套了一层{_value:xxx}ref定义的变量,需要通过.value访问,reactive定义的变量直接访问ref对普通数据类型做了优化,不用依赖于proxy
ref 和 reactive 都有解构的风险,都可以用 const 处理
tips 直接打印
ref定义的显示为Ref类型;打印.value显示为Reactive类型
<script setup>
import { ref,reactive } from 'vue'
const msg = ref({
a:1,
b:2
})
msg.value = {a:2} // "a": 2 }
const msg2 = reactive({
a:1,
b:2
})
// 网上 reactive 派一般是怎么处理对象赋值,但是有明显缺陷
Object.assign(msg2,{a:2}) // { "a": 2, "b": 2 }
</script>
<template>
<h1>{{ msg }}</h1>
<h1>{{ msg2 }}</h1>
</template>
简易版依赖搜集派发
参考链接:简单的依赖收集和派发更新
v3.2.7 为例
let activeEffect; // 建一个全局变量,用于存储当前正在收集依赖的 effect 函数
// 全局依赖管理
// WeakMap<target, Map<key, Set<effect>>>
const targetMap = new WeakMap();
class ReactiveEffect {
deps = [];
/**
* 扩展
* constructor(
* public fn: () => T
* ) { }
* 源代码中怎么写赋值是因为,声明的 public 可以自动赋值同名变量
*/
constructor(fn) {
this.fn = fn;
}
run() {
this.fn();
}
}
// 收集依赖
function track(target, key) {
let depMap = targetMap.get(target);
if (!depMap) {
targetMap.set(target, (depMap = new Map()));
}
let dep = depMap.get(key);
if (!dep) {
// 这里是用 createDep 生成的 new Set<ReactiveEffect>(effects)
depMap.set(key, (dep = new Set()));
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
// 派发更新
function trigger(target, key) {
const depMap = targetMap.get(target);
const dep = depMap.get(key);
for (const effect of dep) {
effect.run();
}
}
/**
* 创建一个响应式对象
* @param {Object} obj
*/
function reactive(obj) {
const proxy = new Proxy(obj, {
get(target, key) {
// Reflect 是 es6 引入的一个内置的对象,它提供了一系列的方法来操作对象
const result = Reflect.get(target, key, proxy);
track(target, key);
return result;
},
set(target, key, value) {
const result = Reflect.set(target, key, value, proxy);
trigger(target, key);
return result;
},
});
return proxy;
}
/**
* 定义一个 effect 函数,用于收集依赖
* @param {function} fn
*/
function effect(fn) {
activeEffect = new ReactiveEffect(fn);
fn();
}
// 使用示例
const person = reactive({
name: "张三",
age: 18,
});
effect(() => {
console.log(`person -> ${person.name} -> ${person.age}`);
});
effect(() => {
console.log(`${person.name} -> ${person.age} -> person`);
});
setTimeout(() => {
person.name = "李四";
}, 1000);
setTimeout(() => {
person.age = 20;
}, 2000);
3.4 map 替代 set
简单例子
// 建一个全局变量,用于存储当前正在收集依赖的 effect 函数
let activeEffect;
// 依赖搜集
const queueEffectSchedulers = [];
let pauseScheduleStack = 0;
class ReactiveEffect {
deps = [];
_trackId=0;
_depsLength=0;
/**
* 扩展
* constructor(
* public fn: () => T
* ) { }
* 源代码中怎么写赋值是因为,声明的 public 可以自动赋值同名变量
*/
constructor(fn) {
this.fn = fn;
}
run() {
this.fn();
}
}
// 收集依赖
function trackEffect(effect, dep) {
if (dep.get(effect) !== effect._trackId) {
dep.set(effect, effect._trackId);
const oldDep = effect.deps[effect._depsLength];
if (oldDep !== dep) {
// 清除旧的依赖
// if (oldDep) {
// cleanupDepEffect(oldDep, effect)
// }
effect.deps[effect._depsLength++] = dep;
} else {
effect._depsLength++;
}
}
}
// 派发更新
function triggerEffects(dep) {
pauseScheduling();
for (const effect of dep.keys()) {
// scheduler 改成 fn,差不多的
queueEffectSchedulers.push(effect.fn);
}
resetScheduling();
}
// 依赖统一触发
function pauseScheduling() {
pauseScheduleStack++;
}
function resetScheduling() {
pauseScheduleStack--;
while (!pauseScheduleStack && queueEffectSchedulers.length) {
// 3.5 的链式触发也是从未端开始的
queueEffectSchedulers.shift()();
}
}
/**
* 创建一个响应式对象
* @param {Object} obj
*/
function ref(obj) {
return new RefImpl(obj);
}
class RefImpl {
constructor(value) {
this._value = value;
}
get value() {
trackEffect(activeEffect, (this.dep ??= new Map()));
return this._value;
}
set value(newVal) {
this._value = newVal;
triggerEffects(this.dep);
}
}
/**
* 定义一个 effect 函数,用于收集依赖
* @param {function} fn
*/
function effect(fn) {
activeEffect = new ReactiveEffect(fn);
fn();
}
// 使用示例
const person = ref(1);
effect(() => {
console.log(`person.value -> ${person.value}`);
});
setTimeout(() => {
person.value = "李四";
}, 1000);
3.5 双向链表
参考链接
- Vue 3.5 双向链表如何实现依赖收集
- Vue 3.5 双向链表 Computed 篇
- Vue3: computed都懒更新了,version计数你还不知道?
- Vue3: 什么是computed的懒更新?不就一个问题的事!
// 建一个全局变量,用于存储当前正在收集依赖的 effect 函数
let activeSub;
let batchDepth = 0;
let batchedSub; // 订阅者执行链表表头
class ReactiveEffect {
deps; // 头部
depsTail; // 尾部
constructor(fn) {
this.fn = fn;
}
run() {
// prepareDeps 把 ink.version = -1
const prevEffect = activeSub;
activeSub = this;
try {
return this.fn();
} finally {
// cleanupDeps(this) 清理依赖
activeSub = prevEffect; // 恢复到上个订阅者
}
}
notify() {
batch(this);
function batch(sub) {
sub.next = batchedSub;
batchedSub = sub; // 指向当前订阅者,多个时会形成链表
}
}
trigger() {
this.run();
}
}
/**
* 定义一个 effect 函数,用于收集依赖
* @param {function} fn
*/
function effect(fn) {
const e = new ReactiveEffect(fn);
e.run();
}
/**
* 创建一个响应式对象
* @param {Object} obj
*/
function ref(obj) {
return new RefImpl(obj);
}
class RefImpl {
dep = new Dep();
constructor(value) {
this._value = value;
}
get value() {
this.dep.track();
return this._value;
}
set value(newValue) {
const oldValue = this._value;
// 判断两者是否相等 Object.is()与===之间的主要区别在于它们如何处理NaN和-0
if (!Object.is(oldValue, newValue)) {
this._value = newValue;
this.dep.trigger();
}
}
}
class Link {
nextDep; // x轴订阅者依赖链表下一个 link
prevDep; // x轴订阅者依赖链表上一个 link
nextSub; // y轴依赖链表下一个 link
prevSub; // y轴依赖链表上一个 link
constructor(sub, dep) {
this.sub = sub;
this.dep = dep;
this.version = dep.version;
this.nextDep =
this.prevDep =
this.nextSub =
this.prevSub =
this.prevActiveLink =
undefined;
}
}
class Dep {
activeLink = undefined; // 当前 link
version = 0;
subs; // 链表尾部指向
// 依赖搜集
track() {
if (!activeSub) return;
let link = this.activeLink;
if (link === undefined || link.sub !== activeSub) {
link = this.activeLink = new Link(activeSub, this);
if (!activeSub.deps) {
activeSub.deps = activeSub.depsTail = link;
} else {
// 订阅者 link 编织
// 与订阅者的链表最后一个 link 编织,形成新链表
link.prevDep = activeSub.depsTail;
activeSub.depsTail.nextDep = link;
activeSub.depsTail = link;
}
// 依赖 link 编织
addSub(link);
function addSub(link) {
const currentTail = link.dep.subs; // 链表尾部指向
// 更新链表尾部指向当前 link
if (currentTail !== link) {
link.prevSub = currentTail;
if (currentTail) currentTail.nextSub = link;
}
link.dep.subs = link; // 更新依赖的链表尾部指向
}
} else if (link.version === -1) {
// 简易版可以忽略下面的逻辑
// -1 代表link已经失效,会被清理
link.version = this.version;
if (link.nextDep) {
// 把 link 从原订阅者的链表中移除
const next = link.nextDep;
next.prevDep = link.prevDep;
if (link.prevDep) {
link.prevDep.nextDep = next;
}
// 把 link 放到新订阅者的链表尾部
link.prevDep = activeSub.depsTail;
link.nextDep = undefined;
activeSub.depsTail.nextDep = link;
activeSub.depsTail = link;
// 指向新的link链表头部
if (activeSub.deps === link) {
activeSub.deps = next;
}
}
}
return link;
}
// 依赖触发
trigger() {
this.version++;
this.notify();
}
// 通知订阅者
notify() {
startBatch();
try {
// 从尾部开始,遍历依赖的 link
for (let link = this.subs; link; link = link.prevSub) {
// 触发 effect.notify
link.sub.notify();
}
} finally {
endBatch();
}
}
}
function startBatch() {
batchDepth++;
}
function endBatch() {
if (--batchDepth > 0) {
return;
}
while (batchedSub) {
let e = batchedSub;
batchedSub = undefined;
while (e) {
const next = e.next;
e.next = undefined;
e.trigger();
e = next;
}
}
}
// 使用示例
const person = ref(1);
effect(() => {
console.log(`person.value -> ${person.value}`);
});
setTimeout(() => {
person.value = "李四";
}, 1000);
求内推
广州/深圳-前端开发