Vue3计算属性

文章详细解释了Vue.js中计算属性的工作原理,包括依赖收集、effect函数的角色以及如何在数据变化时触发重新计算和更新。当data.count改变时,计算属性plusOne的关联effect会重新执行,这是因为计算属性的getter在访问时会检查是否需要重新计算,并且通过effect的依赖收集机制确保了更新的正确顺序。
摘要由CSDN通过智能技术生成

计算属性

// 1. 响应式数据
const data = reactive({ count: 0 });
// 2. 计算属性
const plusOne = computed(() => data.count + 1);
// 3. 依赖收集
effect(() => console.log(plusOne.value));
// 4. 触发上面的effect重新执行
data.count++;

为什么data.count的改变能间接触发访问了计算属性的 effect 的重新执行呢?

首先看一下简化版本的computed的代码

export function computed(getter) {
  let dirty = true;
  let value: T;

  // 这里还是利用了effect做依赖收集
  const runner = effect(getter, {
    // 这里保证初始化的时候不去执行getter
    lazy: true, //表示effect函数返回的runner并不会立即执行,懒加载
    computed: true, // 表示这是一个computed effect,用于trigger阶段的优先级排序
    // 调度执行顺序的实现
    scheduler: () => {
      // 在触发更新时 只是把dirty置为true
      // 而不去立刻计算值 所以计算属性有lazy的特性
      dirty = true;
    },
  });
  
  return {
    get value() {
      if (dirty) {
        // 在真正的去获取计算属性的value的时候
        // 依据dirty的值决定去不去重新执行getter 获取最新值
        value = runner();
        dirty = false;
      }
      // 这里是关键 后续讲解
      trackChildRun(runner);
      return value;
    },
    set value(newValue: T) {
      setter(newValue);
    },
  };
}

首先要知道,effect 函数会立即开始执行,再执行之前,先把effect自身变成全局的activeEffect,以供响应式数据收集依赖。

并且activeEffect的记录是用的栈的方式,随着函数的开始进行入栈,随着函数的结束出栈。这样就可以维护嵌套的effect关系。

先起几个别名便于讲解

// 计算effect
const plusOne =computed(() => data.count + 1);
// 日志effect
effect(() => console.log(plusOne.value));

从依赖关系来看,
日志effect读取了计算effect
计算effect读取了响应式属性count
所以更新的顺序也应该是:

count改变` -> `计算effect更新` -> `日志effect更新

解读

当日志effect开始执行的时候,此时的activeEffect是日志effect,此时的 effectStack 是[ 日志 effect ]

执行日志effect读取了plusOne.value,进而触发了

 get value() {
      if (dirty) {
        // 在真正的去获取计算属性的value的时候
        // 依据dirty的值决定去不去重新执行getter 获取最新值
        value = runner()
        dirty = false
      }
      // 这里是关键 后续讲解
      trackChildRun(runner)
      return value
},
首先进入了求值过程:value = runner(),runner 其实就是计算effect,它是对于用户传入的 getter 函数的包装
进入runner()函数之后,此时的activeEffect就是计算effect,此时的 `effectStack` 是[ 日志 effect, 计算 effect]

runner 所包裹的() => data.count + 1也就是计算effect会去读取count,因为是由 effect 包裹的函数,所以触发了响应式数据的get拦截:

此时count会收集计算effect作为自己的依赖。

并且计算effect会收集count的依赖集合,保存在自己身上。(通过effect.deps属性)

dep.add(activeEffect);
activeEffect.deps.push(dep);

也就是形成了一个双向收集的关系,于是计算effect有了count的所有依赖,count也存了计算effect的依赖。

然后在 runner 运行结束后,计算effect出栈了,此时activeEffect变成了栈顶的日志effect

此时activeEffect是日志 effect,此时的effectStack是[ 日志 effect ]

接下来进入关键的步骤:`trackChildRun()`
trackChildRun(runner);

function trackChildRun(childRunner: ReactiveEffect) {
  for (let i = 0; i < childRunner.deps.length; i++) {
    const dep = childRunner.deps[i];
    dep.add(activeEffect);
  }
}

这个runner就是计算effect,它的deps上此时挂着count的依赖集合,

trackChildRun中,它把当前的 activeEffect 也就是日志effect也加入到了count的依赖集合中。

此时count的依赖集合是这样的:[ 计算effect, 日志effect ]

这样下次count更新的时候,会把两个 effect 都重新触发,而由于触发的顺序是先触发computed effect 后触发普通effect,因此就完成了

  1. 计算 effect 的 dirty 置为 true,标志着下次读取需要重新求值。
  2. 日志 effect 读取计算 effect 的 value,获得最新的值并打印出来。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值