1、执行下面代码
<script src="../../dist/vue.global.js"></script>
<div id="demo">
<h1>
<header>{{a}}</header>
</h1>
</div>
<script>
const { createApp, toRefs, reactive} = Vue
var app = createApp({
setup(){
var obj = reactive({
a:1
})
setInterval(() => {
obj.a = obj.a + 1
}, 1000);
return {...toRefs(obj)}
}
})
app.mount('#demo')
</script>
2、断点分析
ctrl + p输入packages/reactivity/src/effect.ts,按enter找到该文件ctrl + f输入targetMap.get(target),可以找到两个位置,都要打上断点,分别对应着函数track和 函数trigger,能触发函数track的方式有很多,但是大多数都是无效的,所以在if (shouldTrack && activeEffect) {条件内部打断点就可以过滤掉无效的track触发了,直接找出最有效的关键条件ctrl + f输入dep.add(activeEffect!),可以找到该位置,打上断点ctrl + p输入packages\runtime-core\src\renderer.ts,按enter找到该文件ctrl + f输入initialVNode.el = subTree.el,可以找到该位置,打上断点ctrl + f输入next.el = nextTree.el,可以找到该位置,打上断点- 刷新页面,点击断点跳转,可以看到下面的流程:
- 第一次触发断点的是执行
setupRenderEffect函数,内部触发了componentUpdateFn内的的render函数,执行视图的render函数,需要读取属性a的值,所以会触发track进行依赖收集。 - 第二次触发断点的是执行
track内部的trackEffect,走的是满足if (shouldTrack) {这个条件的流程 - 第三次触发断点时,页面从空白中渲染出了
1,因为此时首次patch( null , subTree )完成了 - 第四次触发断点的是执行
setupComponent函数,进一步执行setup函数内的setInterval的obj.a = 2,这个赋值操作触发了trigger,内部执行了effect.scheduler()、queueJob、queueFlush建立一个Promise任务,等到真正执行Promise任务时进一步触发flushJobs- 执行
flushJobs其实又是执行了一次componentUpdateFn,只不过这一次走的是挂载完成时 const nextTree = renderComponentRoot(instance)得到新的虚拟树,在这期间会执行render,进一步访问到了a属性,于是再次触发了track
- 执行
- 第五次触发断点的是执行
track,因为之前已经收集过了,此时shouldTrack = false - 第六次触发断点时,
patch( prevTree , nextTree )执行完这个,页面就从1变成2了 - 第七次触发断点时,重复4操作,只不过此时赋值是
obj.a = 3,而触发trigger - 第八次触发断点时,重复5操作
- 第九次触发断点时,重复6操作,只不过此时,页面从
2变成3了 - 。。。无限重复4、5、6的操作
- 第一次触发断点的是执行
3、补充一下ts小知识,ReactiveEffect类函数用的到
安装cnpm install -g typescript
class MMM{
constructor(public a){}
}
var p = new MMM(1);
console.log(p.a)
执行 tsc index.ts
var MMM = /** @class */ (function () {
function MMM(a) {
this.a = a;
}
return MMM;
}());
var p = new MMM(1);
console.log(p.a); // 打印出1
结论:public 声明的形参a,其实会this.a = a
4、track函数代码分析
targetMap 是一个WeakMap,它的key为用户设置的reactive对象,它的value为Map,该Map的key为用户设置的reative对象中每一个key,该Map的key对应的value,是访问了对象中的key所对应的依赖,且是一个Set结构,也就是说,同一个key可以有多个依赖
const targetMap = new WeakMap()
// targetMap = new WeakMap([target]:new Map( [key]:new Set()) )
export function track(target, type, key) { // target = { a : 1 } type = "get" key = "a"
if (shouldTrack && activeEffect) {
let depsMap = targetMap.get(target)
if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } // 没有就设置
let dep = depsMap.get(key)
if (!dep) { depsMap.set(key, (dep = createDep())) } // dep = Set{n:0,w:0,size:0}
trackEffects(dep, eventInfo)
}
}
const createDep = () => {
const dep = new Set()
dep.w = 0
dep.n = 0
return dep
}
const wasTracked = (dep) => (dep.w & 1) > 0
function trackEffects( dep , debuggerEventExtraInfo ) {
let shouldTrack = false
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
dep.n |= trackOpBit // dep = Set{n:2,w:0,size:0} trackOpBit是个变量
shouldTrack = !wasTracked(dep) // 之前没有追踪过,所以这里shouldTrack = true
}
} else {
shouldTrack = !dep.has(activeEffect)
}
if (shouldTrack) {
dep.add(activeEffect) // dep = Set { 0:ReactiveEffect2 , n:2 , w:0 , size:1 }
activeEffect.deps.push(dep)
/**
activeEffect = {
active: true
allowRecurse: true
deps: [Set(1)]
fn: () => {…}
parent: undefined
scheduler: () => queueJob(instance.update)
}*/
}
}
5、trigger函数代码分析
function trigger(target,type,key,newValue,oldValue,oldTarget) { // target = {a:2}
const depsMap = targetMap.get(target)//targetMap = WeakMap{Object => Map(1)}
if (!depsMap) {
return
}
let deps = []
if (type === TriggerOpTypes.CLEAR) {
deps = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
deps.push(dep)
}
})
} else {
if (key !== void 0) {
deps.push(depsMap.get(key)) // deps = Set{ ReactiveEffect2 }
}
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.DELETE:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
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)
}
}
triggerEffects(createDep(effects))
}
}
export function triggerEffects(dep,debuggerEventExtraInfo) {
for (const effect of isArray(dep) ? dep : [...dep]) {
if (effect !== activeEffect || effect.allowRecurse) {
if (effect.scheduler) {
effect.scheduler() // 其实就是执行了 () => queueJob(instance.update)
} else {
effect.run()
}
}
}
}
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(instance.update)// instance.update = componentUpdateFn
))
const update = ( instance.update = effect.run.bind(effect) )
update.id = instance.uid
class ReactiveEffect{
// this.fn = fn;
// this.scheduler = scheduler
constructor(public fn , public scheduler) {}
run() {
if (!this.active) {
return this.fn()
}
}
}
const queue = []
//queueJob(instance.update);
//instance.update = componentUpdateFn
function queueJob(job) {
//queue.includes(job,index) 这里的index是查询的起始下标
if (
(!queue.length ||
!queue.includes(
job,
isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
)) &&
job !== currentPreFlushParentJob
) {
if (job.id == null) {
queue.push(job)
} else {
queue.splice(findInsertionIndex(job.id), 0, job)//job.id = 0
}
queueFlush()
}
}
const resolvedPromise = Promise.resolve()
function queueFlush() {
if (!isFlushing && !isFlushPending) {
isFlushPending = true
currentFlushPromise = resolvedPromise.then(flushJobs)
}
}
function flushJobs() {
isFlushPending = false
isFlushing = true
flushPreFlushCbs()
queue.sort((a, b) => getId(a) - getId(b))
try {
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex]
if (job && job.active !== false) {
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
}
}
} finally {
flushIndex = 0
queue.length = 0 // 清空队列
flushPostFlushCbs()
isFlushing = false
currentFlushPromise = null
if (
queue.length ||
pendingPreFlushCbs.length ||
pendingPostFlushCbs.length
) {
flushJobs()
}
}
}