揭秘Vue3源码之computed:懒计算与缓存机制全解析

前言

在Vue3中,computed是个神奇的存在。你可能已经在项目里用得飞起,但你有没有想过它到底是怎么工作的?

  • 为什么computed是懒惰的?
  • 它和watch有什么本质区别?
  • Vue3是如何优化computed让它更高效的?

今天就带着这些疑问,走进computed的源码世界,把它从头到脚扒个干净!

本文参考源码版本:3.5.13


计算属性的日常------一个生动的栗子

想象一下,你是个摸鱼大师,每天都要计算"摸鱼时长"(computed)。但只有当老板盯着你(依赖变化)时,你才会重新计算,以免被发现摸太久。

ts 复制代码
<template>
  <div>
    <p>上班时间:{{ workHours }}</p>
    <p>摸鱼时长:{{ fishingTime }}</p>
    <button @click="changeWorkHours">修改工时</button>
  </div>
</template>
​
<script setup>
import { ref, computed } from 'vue';
​
const workHours = ref(8);
const fishingTime = computed(() => 12 - workHours.value);
​
const changeWorkHours = () => {
  workHours.value = Math.floor(Math.random() * 12);
};
</script>

这个computed计算出的fishingTime(摸鱼时长)只有在workHours变化时才会更新,否则一直复用上次的计算结果,省时省力。

那么computed是如何实现这个"懒惰"的特性的呢?让我们深入源码一探究竟!


computed的源码揭秘

computed函数

我们从computed的入口packages/reactivity/src/computed.ts开始:

ts 复制代码
export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
  debugOptions?: DebuggerOptions,
  isSSR = false
) {
  let getter: ComputedGetter<T>;
  let setter: ComputedSetter<T> | undefined;
​
  if (isFunction(getterOrOptions)) {
    getter = getterOrOptions;
  } else {
    getter = getterOrOptions.get;
    setter = getterOrOptions.set;
  }
​
  return new ComputedRefImpl(getter, setter, isSSR);
}

这段代码干了几件事:

  • 区分传入的是函数(只读)还是对象(可写)。
  • 创建ComputedRefImpl实例。

所以,computed的核心逻辑其实都藏在ComputedRefImpl里。接下来,让我们看看这个类是如何让computed变得聪明的。

ComputedRefImpl的核心机制

ts 复制代码
export class ComputedRefImpl<T = any> implements Subscriber {
  _value: any = undefined; // 缓存计算后的值
  readonly dep: Dep = new Dep(this); // 依赖管理
  readonly __v_isRef = true; // 标记为 ref
  readonly __v_isReadonly: boolean;
  flags: EffectFlags = EffectFlags.DIRTY; // 标记 dirty 状态
  globalVersion: number = globalVersion - 1; // 依赖版本
  isSSR: boolean;
  effect: this = this; // 兼容 effect API
​
  constructor(
    public fn: ComputedGetter<T>,
    private readonly setter: ComputedSetter<T> | undefined,
    isSSR: boolean
  ) {
    this.__v_isReadonly = !setter;
    this.isSSR = isSSR;
  }

核心点:

  • _value:缓存计算结果,避免重复计算。
  • dep:管理依赖关系。
  • flagsEffectFlags.DIRTY表示"该计算属性需要重新计算"。

valuegetter

ts 复制代码
get value(): T {
  this.dep.track(); // 依赖收集
  refreshComputed(this); // 关键!刷新计算值
  return this._value;
}

它做了什么?

  1. 依赖收集,让computed知道谁在用它。
  2. 调用refreshComputed,如果需要,重新计算。
  3. 返回缓存的_value,避免重复计算。

refreshComputed函数(简化版)

ts 复制代码
export function refreshComputed(computed: ComputedRefImpl): undefined {
  if (!(computed.flags & EffectFlags.DIRTY)) return;
  computed.flags &= ~EffectFlags.DIRTY;
​
  try {
    computed._value = computed.fn();
  } catch (err) {
    throw err;
  }
}

这个函数的作用是:确保只有当计算属性真的需要更新时,才执行计算。

valuesetter(仅适用于可写computed

ts 复制代码
set value(newValue) {
  if (this.setter) {
    this.setter(newValue);
  } else {
    console.warn('Write operation failed: computed value is readonly');
  }
}
  • 如果是可写computed ,调用setter
  • 如果是只读computed,警告你"别瞎改"!

栗子

回头看看一开始的栗子,当老板盯着你(依赖变化)时,进入工作状态,重新计算"摸鱼时长"(computed)


computed的惰性原理

Vue3的computed依赖ReactiveEffect来管理更新:

  1. 首次访问value

    • 发现是"脏的"(flags = DIRTY),所以执行getter,计算值并缓存。
    • 计算完成后,标记为"干净的"(flags = 0),下次访问直接返回缓存。
  2. 依赖变化时

    • 计算属性不会立刻计算,而是先把flags设为DIRTY,等到下一次访问时才计算。

这就是"懒计算"的精髓!


computed与其他响应式API的对比

在Vue3中,除了computed外,还有watchwatchEffect 以及直接使用reactive数据等方式来处理响应式数据。它们各自的特点和适用场景如下:

  1. computed vs reactive/ref

    • reactive/ref:用于创建基础响应式数据,当数据变化时,任何依赖它的部分都会立即收到更新。
    • computed :建立在reactive/ref的基础上,computed的值是派生的、只读的(除非提供setter),并且具有缓存特性。对于依赖项未发生变化的情况下,多次访问 computed 时不会重新计算,有效减少不必要的计算量。
  2. computed vs watch/watchEffect

    • watch/watchEffect :主要用于副作用操作,比如API调用、DOM操作或触发异步任务。watch在数据变化时会立即执行回调,并且可以访问旧值和新值。
    • computed:关注于数据的派生和缓存,不适合放置副作用逻辑。它在访问时根据依赖进行计算,且结果会被缓存,适用于那些需要频繁访问但不希望每次都重新计算的场景。

综上所述,如果目标是生成一个新的值并且希望它具备缓存功能,应选择computed;如果需要在数据变化时触发某些副作用,则watchwatchEffect更为合适。

特性 computed watch/watchEffect
计算时机 访问value时计算 依赖变化时立即执行
是否缓存
适用场景 计算新值 监听副作用(API等)

结论

computedVue3响应式系统中的重要一环,它通过ReactiveEffect实现了惰性计算缓存机制,让我们的代码更加高效。

在使用computed时,牢记以下几点:

  • 它是惰性求值 的,只有当你访问value时才会计算。
  • 依赖变化时,它不会立即重新计算,而是等你下次访问时才更新
  • computed会缓存结果,除非依赖变了,否则不会重复计算。

感谢阅读!欢迎点赞收藏关注,一键三连!!!

Vue3 源码解析系列

  1. 手摸手带你阅读Vue3源码之Reactive 上
  2. 手摸手带你阅读Vue3源码之Reactive 下
  3. Vue3源码解析之Ref、Effect
  4. Vue3源码解析之nextTick:拯救"数据变了但 DOM 还没反应过来"的尴尬场面
  5. Vue3源码:5个问题带你读懂watch
  6. 揭秘Vue3源码之computed:懒计算与缓存机制全解析
相关推荐
6武71 分钟前
Vue 数据传递流程图指南
前端·javascript·vue.js
goto_w5 分钟前
uniapp上使用webview与浏览器交互,支持三端(android、iOS、harmonyos next)
android·vue.js·ios·uni-app·harmonyos
高 朗38 分钟前
2025高频面试设计模型总结篇
设计模式·面试·职场和发展
jakeswang1 小时前
查询条件与查询数据的ajax拼装
前端·ajax
samuel9181 小时前
axios取消重复请求
前端·javascript·vue.js
三天不学习1 小时前
JiebaAnalyzer 分词模式详解【搜索引擎系列教程】
前端·搜索引擎·jiebaanalyzer
苹果酱05671 小时前
Golang标准库——runtime
java·vue.js·spring boot·mysql·课程设计
滿1 小时前
Vue 3 中按照某个字段将数组分成多个数组
前端·javascript·vue.js
安分小尧1 小时前
[特殊字符] 使用 Handsontable 构建一个支持 Excel 公式计算的动态表格
前端·javascript·react.js·typescript·excel