依赖收集和触发ref和reactive分开讨论
公共部分
从patch开始
- patch触发了processComponent
- processComponent触发mountComponent
- mountComponent触发setupComponent、setupRenderEffect
- setupComponent中会执行setup函数,里面会有ref、reactive,并返回一个用来执行h的函数,赋值给instance.render(执行setup的时候就会把数据变成响应式的,但还没有收集)
setup() {
const refMsg = ref('1111')
const refMsg2 = ref('3333')
return () => {
return h("div", {}, `111${refMsg.value}`);
};
},
const setupResult = setup && setup(shallowReadonly(instance.props), setupContext);
instance.render = setupResult;
setupRenderEffect函数中回执行
12行执行run,我们要拿出来重点分析
function componentUpdateFn() {
const subTree = (instance.subTree = instance.render.call(proxyToUse, proxyToUse));
}
instance.update = effect(componentUpdateFn, {
scheduler: () => {
queueJob(instance.update);
},
});
// effect函数
function effect(fn, options = {}) {
const _effect = new ReactiveEffect(fn);
_effect.run();
const runner = _effect.run.bind(_effect);
}
收集依赖的类ReactiveEffect
每次setup都会new ReactiveEffect(); 在ReactiveEffect中有deps收集依赖,也就把diff控制在了组件级。
class ReactiveEffect {
constructor(fn, scheduler) {
this.fn = fn;
this.scheduler = scheduler;
this.active = true;
this.deps = [];
}
run() {
if (!this.active) {
return this.fn();
}
shouldTrack = true;
activeEffect = this;
const result = this.fn();
shouldTrack = false;
activeEffect = undefined;
return result;
}
stop() {
if (this.active) {
cleanupEffect(this);
if (this.onStop) {
this.onStop();
}
this.active = false;
}
}
}
ReactiveEffect中run
可以看到const result = this.fn();前后的操作,我们可以理解为:收集依赖的开关,this.fn()之前开启,之后关闭。那么this.fn()做了什么(fn就是上面的componentUpdateFn)
run() {
if (!this.active) {
return this.fn();
}
shouldTrack = true;
activeEffect = this;
const result = this.fn();
shouldTrack = false;
activeEffect = undefined;
return result;
}
执行h函数触发get,componentUpdateFn
通过isMounted做了区分:挂载过、未挂载过。但是两种都会执行instance.render,也就回执行setup函数返回的h函数。
h函数中如果用到了响应式的数据,则回触发该数据的get,下面我们分析get
function componentUpdateFn() {
if (!instance.isMounted) {
const proxyToUse = instance.proxy;
const subTree = (instance.subTree = instance.render.call(proxyToUse, proxyToUse));
patch(null, subTree, container, instance);
initialVNode.el = subTree.el;
instance.isMounted = true;
}
else {
const { next, vnode } = instance;
if (next) {
next.el = vnode.el;
updateComponentPreRender(instance, next);
}
const proxyToUse = instance.proxy;
const nextTree = instance.render.call(proxyToUse, proxyToUse);
const prevTree = instance.subTree;
instance.subTree = nextTree;
patch(prevTree, nextTree, prevTree.el, instance);
}
}
setup() {
const refMsg = ref('1111')
const refMsg2 = ref('3333')
return () => {
return h("div", {}, `111${refMsg.value}`);
};
},
ref
get收集依赖
class RefImpl {
constructor(value) {
this.__v_isRef = true;
this._rawValue = value;
this._value = convert(value);
this.dep = createDep();
}
get value() {
// console.log('触发get,收集')
trackRefValue(this);
return this._value;
}
set value(newValue) {
if (hasChanged(newValue, this._rawValue)) {
this._value = convert(newValue);
this._rawValue = newValue;
triggerRefValue(this);
}
}
}
trackRefValue
- trackRefValue参数是RefImpl的实例,isTracking是个开关,shouldTrack=true,activeEffect是ReactiveEffect的实例。
- trackEffects把activeEffect添加进RefImpl的dep中。每个RefImpl都保存了当前组件所有的依赖。
- trackEffects把RefImpl的dep放进了activeEffect的deps中,每个ref都会在activeEffect.deps中放进一个依赖。
function trackRefValue(ref) {
if (isTracking()) {
trackEffects(ref.dep);
}
}
function isTracking() {
return shouldTrack && activeEffect !== undefined;
}
function trackEffects(dep) {
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
set触发依赖
class RefImpl {
constructor(value) {
this.__v_isRef = true;
this._rawValue = value;
this._value = convert(value);
this.dep = createDep();
}
get value() {
trackRefValue(this);
return this._value;
}
set value(newValue) {
if (hasChanged(newValue, this._rawValue)) {
this._value = convert(newValue);
this._rawValue = newValue;
// console.log('触发依赖')
triggerRefValue(this);
}
}
}
triggerRefValue
function triggerRefValue(ref) {
triggerEffects(ref.dep);
}
function triggerEffects(dep) {
for (const effect of dep) {
if (effect.scheduler) {
effect.scheduler();
}
else {
effect.run();
}
}
}
scheduler
当!queue.includes(job),才会执行queueFlush,也就意味着组件触发多个依赖时,只会触发一次更新
instance.update = effect(componentUpdateFn, {
scheduler: () => {
queueJob(instance.update);
},
});
function queueJob(job) {
if (!queue.includes(job)) {
queue.push(job);
queueFlush();
}
}
function flushJobs() {
isFlushPending = false;
let job;
while ((job = queue.shift())) {
if (job) {
job();
}
}
}
function queueFlush() {
if (isFlushPending)
return;
isFlushPending = true;
nextTick(flushJobs);
}
function nextTick(fn) {
return fn ? p.then(fn) : p;
}
还记得第19行job是什么吗?
job就是effect执行时返回的runner,用来执行run
22行的fn正是componentUpdateFn,会执行patch
instance.update = effect(componentUpdateFn, {
scheduler: () => {
queueJob(instance.update);
},
});
function effect(fn, options = {}) {
const _effect = new ReactiveEffect(fn);
extend(_effect, options);
_effect.run();
const runner = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
//简化过
class ReactiveEffect {
constructor(fn, scheduler) {
this.fn = fn;
}
run() {
shouldTrack = true;
activeEffect = this;
const result = this.fn();
shouldTrack = false;
activeEffect = undefined;
return result;
}
}
reactive
get收集依赖
const proxy = new Proxy(target, baseHandlers);
baseHandlers = {
get:
}
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
...
if (!isReadonly) {
track(target, "get", key);
}
...
};
}
track函数
- targetMap存储依赖
- isTracking作用和上面在ref中一样
- targetMap中存一个数据:key是target,value是一个depsMap(类型Map)
- 给上面的Map存入数据:key是参数key,value是dep(类型Set)
const targetMap = new WeakMap();
function track(target, type, key) {
if (!isTracking()) {
return;
}
// console.log(`触发 track -> target: ${target} type:${type} key:${key}`);
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);
}
trackEffects函数
activeEffect就是ReactiveEffect的实例,把activeEffect存在targetMap中通过target、key获取
function trackEffects(dep) {
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
set触发依赖
function createSetter() {
return function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, "get", key);
return result;
};
}
trigger函数
- 通过target、key从targetMap中获取dep
- 把dep放进effects中,用createDep去重一下
function trigger(target, type, key) {
let deps = [];
const depsMap = targetMap.get(target);
const dep = depsMap.get(key);
deps.push(dep);
const effects = [];
deps.forEach((dep) => {
effects.push(...dep);
});
triggerEffects(createDep(effects));
}
function createDep(effects) {
const dep = new Set(effects);
return dep;
}
triggerEffects函数
变量执行effect.scheduler();
function triggerEffects(dep) {
for (const effect of dep) {
if (effect.scheduler) {
effect.scheduler();
}
else {
effect.run();
}
}
}
scheduler同上面的ref中的scheduler
总结
ref和reactive的区别在于依赖存储位置不同,触发依赖时拿取依赖的位置也就不同
- ref是存在RefImpl实例中
- reactive是存在公共targetMap中