vue3的reactive原理

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、断点分析

  1. ctrl + p 输入packages/reactivity/src/effect.ts,按enter找到该文件
  2. ctrl + f 输入targetMap.get(target),可以找到两个位置,都要打上断点,分别对应着函数track 和 函数trigger,能触发函数track的方式有很多,但是大多数都是无效的,所以在if (shouldTrack && activeEffect) {条件内部打断点就可以过滤掉无效的track触发了,直接找出最有效的关键条件
  3. ctrl + f 输入 dep.add(activeEffect!),可以找到该位置,打上断点
  4. ctrl + p 输入packages\runtime-core\src\renderer.ts,按enter找到该文件
  5. ctrl + f 输入 initialVNode.el = subTree.el,可以找到该位置,打上断点
  6. ctrl + f 输入 next.el = nextTree.el,可以找到该位置,打上断点
  7. 刷新页面,点击断点跳转,可以看到下面的流程:
    1. 第一次触发断点的是执行setupRenderEffect函数,内部触发了componentUpdateFn内的的render函数,执行视图的render函数,需要读取属性a的值,所以会触发track进行依赖收集。
    2. 第二次触发断点的是执行track内部的trackEffect,走的是满足if (shouldTrack) {这个条件的流程
    3. 第三次触发断点时,页面从空白中渲染出了1,因为此时首次patch( null , subTree )完成了
    4. 第四次触发断点的是执行setupComponent函数,进一步执行setup函数内的setIntervalobj.a = 2,这个赋值操作触发了trigger,内部执行了effect.scheduler()queueJobqueueFlush建立一个Promise任务,等到真正执行Promise任务时进一步触发flushJobs
      1. 执行flushJobs其实又是执行了一次componentUpdateFn,只不过这一次走的是挂载完成时
      2. const nextTree = renderComponentRoot(instance)得到新的虚拟树,在这期间会执行render,进一步访问到了a属性,于是再次触发了track
    5. 第五次触发断点的是执行track,因为之前已经收集过了,此时shouldTrack = false
    6. 第六次触发断点时,patch( prevTree , nextTree ) 执行完这个,页面就从 1 变成 2
    7. 第七次触发断点时,重复4操作,只不过此时赋值是obj.a = 3,而触发trigger
    8. 第八次触发断点时,重复5操作
    9. 第九次触发断点时,重复6操作,只不过此时,页面从 2 变成 3
    10. 。。。无限重复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
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值