Vue3响应式个人理解(六)计算属性Computed与lazy

文章参考了霍春阳的《Vue.js设计与实现》,是自己在阅读过程中的一些思考和理解

有些场景中,effect函数的执行并不是需要立即的。因此在options中加入lazy属性来判断当前的副作用函数是否需要立即执行。代码如下:

// 新增调度选项挂载
effectfn.options = options
effectfn.deps = []
//非lzay情况下直接执行
if(!options.lazy) {   // <---新增条件
  effectfn()
}
// 否则返回副作用函数
return effectfn

我们获得了副作用函数的返回,就能按照自己的逻辑来控制其在何处何时应该执行。现在将传入的副作用函数看作一个getter,则该函数就可以获得返回值。代码如下:

const effectfn = effect(
  // 视作一个getter,获得返回值
  () => proxy.text    //  <---此处修改
, {
  // 调度器
  scheduler(fn) {
    queue.add(fn)
    flushJob()
  },
  // lazy标记
  lazy: true
})

const res = effectfn()  //  <--这样就能拿到其值

但是effect的实现中并没有返回值,只返回了一个副作用函数的执行,因此需要在effectfn中添加返回值。代码如下:

    const effectfn = () => {
      clearup(effectfn)
      activeEffect = effectfn
      // 栈设计
      effectStack.push(effectfn)
      //  将传入的effectfn函数执行结果保存下来
      const value = fn()  // <---新增
      //  出栈
      effectStack.pop()
      //  设置当前全局activeEffect为栈顶effectfn
      activeEffect = effectStack[effectStack.length-1]
      //  返回函数结果    // <---新增
      return value
    }

到此,通过effectfn拿到真正副作用函数的执行返回。进而能实现计算属性。代码如下:

function computed(getter) {
  // 将要获取的数据函数作为getter传入effect函数,同时设置lazy值
  const effectfn = effect(getter, {
    lazy: true
  })
  const obj = {
    // 当读取其值时,才执行副作用函数
    get value() {
      return effectfn()
    }
  }
  return obj
}

这样调用时,便可得到其值:

const res = computed(() => proxy.text)  // <--得到obj
console.log(res.value)    // <--获取其值时调用副作用函数

但是这种情况下,只是做到了懒加载,也就是说达到了我们需要时读取的操作,但是每次的读取依然会调用一次effectfn函数,即使数据没有发生变化,这样就增加了不必要的计算。因此加入了缓存机制。代码如下:

function computed(getter) {
  // 存储计算后的值
  let value
  // 加入标志位,代表是否需要重新计算,刚开始设置为true,代表第一次需要获取
  let dirty = true


  // 将要获取的数据函数作为getter传入effect函数,同时设置lazy值
  const effectfn = effect(getter, {
    lazy: true
  })
  const obj = {
    // 当读取其值时,才执行副作用函数
    get value() {
      // 如果需要重新计算,则调用effectfn,然后赋值给value,否则直接将原数据返回
      if(dirty) {
        value = effectfn()
        dirty = false
      }
      return value
    }
  }
  return obj
}

这样虽然能达到不用多次执行副作用函数的目的,但是dirty的值经过第一次修改后,就无法感知数据变化进而再次调用effectfn了。因此需要在数据发生变化时,让dirty变为true,也就是触发trigger的时候让dirty发生变化。结合上一模块的调度执行,为effect添加调度函数,因为调度器只会在

trigger函数中执行。代码如下:
  // 将要获取的数据函数作为getter传入effect函数,同时设置lazy值
  const effectfn = effect(getter, {
    lazy: true,
    // 新增调度器,数据发生变化时触发
    scheduler() {
      dirty = true
    }
  })

这里添加调度器后,第一次执行时,会将effectfn函数添加到backet中。在发生数据变化时,由于会遍历backet中的函数,进而可以调用scheduler函数,从而使得dirty变为true,使得变化后读取值的操作能够再次执行effectfn函数,达到数据响应的功能。

PS:这里我在变化Number数据类型时,输出的数据变为了NaN。发现原因出在track函数中,因为如果直接在全局中改变代理中的值,没有通过effect函数进行的话,全局变量activeEffect值为Null,返回的数据就为了undefine,这时再进行变化操作就会变为NaN存回原数据,代码如下(修改后):

 get(target, proxy) {
      let res = target[proxy]
      if(!activeEffect) return res

可以发现最开始的时候,是判断如果没有activeEffect的话就直接return,这就是导致出现NaN的原因。因为直接修改不进过effect就没有activeEffect。修改后就可以完成修改操作。进而完成computed函数的功能。

但是这样的computed函数有问题。就是当在一个effect中读取computed属性时,这时我们修改代理属性中的值,会发现computed属性并没有更新,也就是没有数据没有同步。这是因为发生了effect嵌套,对于track函数来说,他里面访问的响应式数据只会把computed内部的effect收集,而如果嵌套了一个在外层的话,内部的effect就不会被内部的收集。

解决办法:当读取计算属性的值时,手动调用track函数;当计算属性依赖的响应式数据发生变化时,手动调用trigger函数触发。代码如下(这里没懂):

const effectfn = effect(getter, {
    lazy: true,
    // 新增调度器,数据发生变化时触发
    scheduler(dirty) {
      dirty = true
      // 响应式数据发生变化时,手动触发trigger
      trigger(obj, 'value')
    }
  })
  const obj = {
    // 当读取其值时,才执行副作用函数
    get value() {
      // 如果需要重新计算,则调用effectfn,然后赋值给value,否则直接将原数据返回
      if(dirty) {
        value = effectfn()
        dirty = false
      }
      // 读取数据时,手动调用track进行跟踪
      track(obj, 'value')
      return value
    }
  }

这里的逻辑可能是这样的:因为computed是lazy的,只有我们读取其值或者亲自在computed属性上修改数据时,才能看到数据的变化,而在外部或者effect函数中读取或修改时,数据虽然发生了变化,但是在其他读取的地方并没有发生相应的变化。所以,我们在computed中加入了手动的调用操作,让其余地方的数据发生变化。

(应该是这样吧,但是我在测试的时候,堆栈溢出了…逆天,这里需要理解)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值