带你从0开始了解vue3核心(computed, watch)

计算属性 computed

计算属性computed会基于其响应式依赖被缓存,并且在依赖的响应式数据发生变化时重新计算。

计算属性也是一个ref对象。

测试用例

  <div id="app"></div>
  <script>
    const { computed, reactive, effect } = Vue;

    const obj = reactive({
      name: "zh"
    })


    const computedObj = computed(() => {
      return "执行" + obj.name
    })

    effect(() => {
      
      document.getElementById("app").innerHTML = computedObj.value
    })

    setTimeout(() => {
      obj.name = "llm"
    }, 2000)
  </script> 

我们先来介绍一下执行流程,再看断点调试。

  • 上来先执行computed,创建一个computedRef对象。(所有computed对象都是Ref对象)

  • 初始化computedRef对象时,创建一个ReactiveEffect对象。并将computed的getter函数传入。

  • 然后执行effect,创建ReactiveEffect对象,并将effect回调传入。

  • 然后computedObj.value触发computed对象的get value, 收集执行effect创建的ReactiveEffect对象。

  • 通过_dirty变量控制computed的getter执行,触发reactive对象的getter方法,收集依赖(收集的是创建computedRef对象时内部创建的ReactiveEffect对象)。(这里非常重要的一点,只要触发computed get value就有可能重新执行computed的getter)

  • 2s后,触发reactive对象的setter方法,触发依赖执行。这里就需要注意了。由于触发的是computedRef对象时内部创建的ReactiveEffect对象,上面挂载的有computed,并且有scheduler调度器,所以会先执行含有computed属性的依赖具有scheduler调度器的依赖。

  • 执行调度器,调度器中触发computed对象get value收集的依赖。此时document.getElementById("app").innerHTML = computedObj.value执行,又触发computed get value, 执行computed 的getter 方法,返回修改的值。

断点调试

  • 初始化computed

  • 触发computed get value,进行依赖收集,并执行computed传入的getter方法

  • 2s后触发reactive setter,然后触发依赖函数。此时该依赖有computed对象,所以调用scheduler调度器

  • 触发effect回调,又会触发computed对象的get value。获取最新值。这里需要注意,虽然2s后触发了reactive的setter方法,但是并没有在trigger中直接执行computed的getter函数,而是通过再次触发computed get value通过_dirty变量来控制getter的触发的。

computed如何做缓存

当我们多次调用computedRef.value时,他会将computed reactiveEffect对象也加入到computedRef对象的deps中,在触发依赖时,如果不特殊处理,就会造成死循环,不会做缓存。

所以我们需要在触发依赖的时候先触发具有computed属性的依赖,再触发普通响应式依赖。

总结

  • 创建computedRefEmpl实例,内部通过_dirty变量判断是否触发依赖。触发依赖放在ReactiveEffect的调度器中执行,这样就可以区分普通的响应式数据和computed响应式数据执行了。并且先去触发computed的依赖函数,再去触发普通响应数据的依赖函数。(这样是为了做到computed缓存的)

  • 获取computed变量时,触发get value执行,然后收集依赖。并执行传入的依赖getter。并修改_dirty为false,如果依赖数据未变化,那么它将返回缓存的值。

只要修改响应式数据,就会触发调度器执行,然后_dirty设置为false,然后就会再次重新执行getter,拿到最新值。

watch

测试用例

const { watch, reactive, effect } = Vue;

const obj = reactive({
  name: "zh"
})


watch(obj, (val) => {
  console.log("val", val)
})

setTimeout(() => {
  obj.name = "llm"
}, 2000)

  • 触发watchCallback

这里需要注意一下,监听对象的变化,我们获取新旧值是一样的,经过上面的分析我们就可以看出,因为oldValue是执行ReactiveEffect中的fn返回的,它返回的是一个对象类型。新值也是这个对象,所以setter修改时,引用不变,所以新旧值是一样的。

断点调试

  • 调用doWatch函数执行初始化watch

  • 进行判断,如果是reactive对象,那么就深度监听。

  • 对象,递归调用,触发依赖收集

  • 定义job函数

  • 初始化ReactiveEffect对象和调度器。

任何关于响应式的api内部都离不开ReactiveEffect类的初始化,他就是通过Proxy get拦截器收集ReactiveEffect对象作为依赖,在触发Proxy set拦截器时,查看是否有scheduler回调(computed 触发get value的回调,watch第二个参数),如果有就执行,没有就执行普通的响应式回调。

watch实现代码[1]

调度器Scheduler

  • 控制代码执行顺序。

const { reactive, effect } = Vue

const obj = reactive({
  age: 1
})

effect(() => {
  console.log("=======", obj.age)
}, {
  scheduler() {
    setTimeout(() => {
      console.log("=======", obj.age)
    })
  }
})

obj.age = 2
console.log("执行结束!")

由上图可知,执行结束先于=====,2输出。

  • 控制代码执行逻辑。

const { reactive, effect, queuePostFlushCb } = Vue

const obj = reactive({
  age: 1
})

effect(() => {
  console.log("=======", obj.age)
}, {
  scheduler() {
    queuePostFlushCb(() => {
      console.log("=======", obj.age)
    })
  }
})

obj.age = 2
obj.age = 3
console.log("执行结束!")

由上图可知,跳过了obj.age = 2的setter逻辑触发。

原理就是通过微任务队列执行调度器中的任务。

经过前面的分析,我们发现,scheduler对于计算属性和watch是非常重要的。

xdm,一起学习vue核心思想吧,为了能突破现状,加油啊。天天对着表单表格看,人都傻掉了。

  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Web面试那些事儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值