1、执行下面代码
在packages\vue\examples\composition\test.html
下
<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可以有多个依赖
在 packages\reactivity\src\effect.ts
下,搜索export function track(target:
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函数代码分析
在 packages\reactivity\src\effect.ts
下,搜索 type: TriggerOpTypes,
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()
}
}
}
6、为什么,执行render中读取a会触发track,更新a值会触发trigger
在packages\reactivity\src\reactive.ts
下搜索function reactive(target: object) {
export function reactive(target: object) {
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
function createReactiveObject(
target,
isReadonly,
baseHandlers
) {
const proxy = new Proxy( // 在这里做了代理了,代理函数是baseHandlers
target,
baseHandlers
)
return proxy
}
const mutableHandlers = {
get,
set,
deleteProperty,
has,
ownKeys
}
const get = createGetter()
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
return res
}
}
const set = createSetter()
function createSetter(shallow = false) {
return function set(
target,
key,
value,
receiver
) {
const result = Reflect.set(target, key, value, receiver)
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
return result
}
}