【Vue3源码学习】— CH2.7 Computed: Vue 3 计算属性深入解析

Computed: Vue 3 计算属性深入解析

  • 1.计算属性的基本用法
  • 2. ComputedRefImpl 类深入解析
    • JavaScript 中的 getter 函数
  • 3. 计算属性的创建:computed 方法解析
    • 3.1 源码解析
    • 3.2 使用示例
  • 4. 计算属性的工作原理
  • 5. 手动实现简化的计算属性
  • 6. 结语

在 Vue 3 的响应式系统中,计算属性(computed)扮演着重要的角色。它们基于响应式依赖进行缓存,并仅在依赖项变化时重新计算。这意味着,计算属性能够提供高效的数据处理方式,因为只有当实际需要时计算属性的值才会更新。下面,我们将详细探讨 Vue 3 中计算属性的实现细节及其用法。

1.计算属性的基本用法

计算属性依赖于其他响应式数据,并且仅在这些依赖数据发生变化时才重新计算其值。这种机制保证了性能的优化,避免了不必要的计算。

import { reactive, computed } from "vue";const state = reactive({count: 1,
});const plusOne = computed(() => state.count + 1);console.log(plusOne.value); // 2
state.count++;
console.log(plusOne.value); // 3

2. ComputedRefImpl 类深入解析

ComputedRefImpl 类是计算属性在 Vue 3 中的实现基础。它负责把用户定义的 getter 函数封装成响应式引用,并且管理计算结果的缓存。该类的关键实现如下:

export class ComputedRefImpl<T> {//用于存储与此计算属性相关的依赖(副作用函数)。public dep?: Dep = undefined//用于存储计算属性的当前值private _value!: T//一个 ReactiveEffect 实例,用于封装计算属性的 getter 函数。这个副作用函数会在依赖的响应式数据变化时重新执行,以更新计算属性的值public readonly effect: ReactiveEffect<T>//内部标志,用于标记这个对象是一个 Ref 类型,并且指示其只读状态。public readonly __v_isRef = truepublic readonly [ReactiveFlags.IS_READONLY]: boolean = falsepublic _cacheable: boolean/*** Dev only*/_warnRecursive?: boolean/*** 构造函数接收四个参数:* getter: 用户定义的计算属性的获取函数。* _setter: 用户定义的计算属性的设置函数,用于允许计算属性被赋新值。* isReadonly: 表明这个计算属性是否是只读的。* isSSR: 标记是否在服务器端渲染环境中使用,影响是否缓存计算结果。*/constructor(private getter: ComputedGetter<T>,private readonly _setter: ComputedSetter<T>,isReadonly: boolean,isSSR: boolean,) {//ReactiveEffect 被用于封装 getter 函数,确保每当依赖的数据变化时,都能够自动重新计算值,并缓存结果以提高性能。this.effect = new ReactiveEffect(() => getter(this._value),() =>triggerRefValue(this,this.effect._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect? DirtyLevels.MaybeDirty_ComputedSideEffect: DirtyLevels.MaybeDirty,),)this.effect.computed = thisthis.effect.active = this._cacheable = !isSSRthis[ReactiveFlags.IS_READONLY] = isReadonly}/*** 当访问计算属性的 value 时,会执行这个 getter 函数。* 这个函数首先检查是否需要重新计算计算属性的值(基于缓存逻辑和依赖数据的变化)。* 如果需要,它会运行封装的 getter 函数来更新 _value。* 然后,它会注册当前活动的副作用函数为这个计算属性的依赖,以便将来数据变化时能触发更新*/get value() {// the computed ref may get wrapped by other proxies e.g. readonly() #3376//“获取当前计算属性实例(this)背后的原始对象,并将其赋值给 self 变量”const self = toRaw(this)if ((!self._cacheable || self.effect.dirty) &&hasChanged(self._value, (self._value = self.effect.run()!))) {triggerRefValue(self, DirtyLevels.Dirty)}trackRefValue(self)if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {if (__DEV__ && (__TEST__ || this._warnRecursive)) {warn(COMPUTED_SIDE_EFFECT_WARN, `\n\ngetter: `, this.getter)}triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)}return self._value}set value(newValue: T) {this._setter(newValue)}// #region polyfill _dirty for backward compatibility third party code for Vue <= 3.3.xget _dirty() {return this.effect.dirty}set _dirty(v) {this.effect.dirty = v}// #endregion
}

这个类通过 ReactiveEffect 封装 getter 函数,使计算属性能够响应依赖数据的变化。同时,通过缓存机制保证了性能的优化。

JavaScript 中的 getter 函数

get value() {} 是 JavaScript 中的一个 getter 函数的写法,它是对象属性访问器的语法之一。Getter 函数允许你定义一个对象属性,该属性在被访问时会自动执行一个函数来返回值,而不是直接返回一个值。这使得在对象属性被访问时可以执行更复杂的操作或计算,而对于使用者来说,这种访问看起来就像访问一个普通属性一样。

const person = {firstName: "John",lastName: "Doe",get fullName() {return `${this.firstName} ${this.lastName}`;}
};console.log(person.fullName); // 输出: John Doe

3. 计算属性的创建:computed 方法解析

Vue 3 提供了 computed 函数,用于创建计算属性。这个函数既可以接受一个简单的 getter 函数,也可以接受一个包含 get 和 set 方法的对象,允许创建可读写的计算属性。

3.1 源码解析

/*** 接受一个 getter 函数作为参数。这个 getter 函数定义了计算属性的计算逻辑,* 当依赖的响应式数据变化时,这个函数会被重新执行来更新计算属性的值。* 在这种情况下,计算属性是只读的,尝试写入会导致警告(在开发模式下)。*/
export function computed<T>(getter: ComputedGetter<T>,debugOptions?: DebuggerOptions,
): ComputedRef<T>/*** 接受一个包含 get 和 set 方法的对象 options 作为参数* 这允许你创建一个可写的计算属性。get 方法定义了计算逻辑,和只读计算属性一样。* set 方法允许你自定义当尝试修改计算属性的值时的行为,这在需要基于计算属性的值反向更新其依赖的响应式数据时非常有用。*/
export function computed<T>(options: WritableComputedOptions<T>,debugOptions?: DebuggerOptions,
): WritableComputedRef<T>export function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,debugOptions?: DebuggerOptions,isSSR = false,
) {let getter: ComputedGetter<T>let setter: ComputedSetter<T>const onlyGetter = isFunction(getterOrOptions)if (onlyGetter) {getter = getterOrOptionssetter = __DEV__? () => {warn('Write operation failed: computed value is readonly')}: NOOP} else {getter = getterOrOptions.getsetter = getterOrOptions.set}//使用 ComputedRefImpl 类来实际创建计算属性const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)if (__DEV__ && debugOptions && !isSSR) {cRef.effect.onTrack = debugOptions.onTrackcRef.effect.onTrigger = debugOptions.onTrigger}return cRef as any
}

3.2 使用示例

import { computed } from "vue";// 创建只读计算属性
const readOnlyComputed = computed(() => someReactiveData.value + 1);// 创建可写计算属性
const writableComputed = computed({get: () => someReactiveData.value + 1,set: (newValue) => { someReactiveData.value = newValue - 1; }
});

4. 计算属性的工作原理

计算属性背后的核心是其延迟计算和缓存机制。ComputedRefImpl 类中的 effect 通过跟踪响应式依赖自动管理这些逻辑,保证了数据的实时性和性能的优化。当依赖数据变化时,计算属性会重新计算;否则,将直接使用缓存的结果。

5. 手动实现简化的计算属性

理解计算属性的实现机制后,我们可以尝试手动实现一个简化版本,以加深对其原理的理解。


function computedManual(getter){const result  = ref();  // 用于存储计算属性的结果const runner = effect(getter,{lazy:true, // 让 effect 不会立即执行scheduler:()=>{// 当依赖变化时,重新计算并更新 result 的值result.value = runner();}})// 立即执行一次 effect,初始化 result 的值result.value = runner();return {// 返回一个具有 value 属性的对象,模拟 ComputedRef 接口get value(){return result.value;},// 提供一个停止响应式依赖更新的方法stop:()=>stop(runner)}
}// 使用示例
const count = ref(1);
const doubled = computedManual(() => count.value * 2);console.log(doubled.value); // 输出: 2
count.value = 2;
console.log(doubled.value); // 输出: 4

这个简化的实现利用了 Vue 的 effect 和 ref,通过设定 lazy 选项来控制副作用函数的执行,同时使用调度器更新计算结果。

6. 结语

计算属性是 Vue 3 响应式系统中不可或缺的一部分,它通过缓存和自动更新机制,有效地优化了数据处理的性能。通过深入理解其背后的实现原理,我们能更好地利用 Vue 提供的响应式功能构建高效的应用。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/798388.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【教程】VOC数据集制作

语义分割任务中VOC数据集的制作&#xff0c;任务中只有一种标签&#xff1a;gas 文章目录 1、由黑白图像识别为txt标签2、txt转json3、数据集转VOC格式 1、由黑白图像识别为txt标签 由于使用CycleGAN网络进行风格迁移学习&#xff0c;生成了大量伪标签图像&#xff0c;因此需…

【递归与递推】数的计算|数的划分|耐摔指数

1.数的计算 - 蓝桥云课 (lanqiao.cn) 思路&#xff1a; 1.dfs的变量>每一次递归什么在变&#xff1f; &#xff08;1&#xff09;当前数的大小一直在变&#xff1a;sum &#xff08;2&#xff09;最高位的数&#xff1a;k 2.递归出口&#xff1a;最高位数字为1 3.注意&#…

鱼塘钓鱼(c++实现)

题目 有 N 个鱼塘排成一排&#xff0c;每个鱼塘中有一定数量的鱼&#xff0c;例如&#xff1a;N5 时&#xff0c;如下表&#xff1a; 即&#xff1a;在第 1 个鱼塘中钓鱼第 1 分钟内可钓到 10 条鱼&#xff0c;第 2 分钟内只能钓到 8 条鱼&#xff0c;……&#xff0c;第 5 分…

Codeforces Round 932 (Div. 2) ---- F. Andrey‘s Tree ---- 题解

F. Andreys Tree&#xff1a; 题目描述&#xff1a; 思路解析&#xff1a; 我们假设删除任意一个结点后&#xff0c;我们会将整个树切分为k个联通块&#xff0c;那么可以明确的知道我们只需要连接(k-1)条边就可以将这k个联通块重新连为一棵树。 那么最小代价是啥呢? 图解分…

0基础进入IT行业

0基础如何进入IT行业&#xff1f; 简介&#xff1a;对于没有任何相关背景知识的人来说&#xff0c;如何才能成功进入IT行业&#xff1f;是否有一些特定的方法或技巧可以帮助他们实现这一目标&#xff1f; 方向一&#xff1a;学习路径 对于零基础进入 IT 行业的人来说&#xff…

第十五题:最大距离

题目描述 在数列 a1,a2,⋯ ,an​中&#xff0c;定义两个元素 ai 和 aj​ 的距离为∣i−j∣∣ai−aj∣&#xff0c;即元素下标的距离加上元素值的差的绝对值&#xff0c;其中 ∣x∣ 表示 x 的绝对值。 给定一个数列&#xff0c;请问找出元素之间最大的元素距离。 输入描述 …

【网站项目】校园订餐小程序

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

鱼骨图功能实现

dom: <div class="module-content"><div class="title"><span>[</span><p>鱼骨图</p><span>]</span></div><div class="line-mian"></div><div :ref="module + i&q…

通过UDP实现参数配置

来讲讲UDP的一种常见应用 我们知道UDP是一种无连接的网络传输协议&#xff0c;在发送数据时指定目标IP及端口就可以将数据发送出去&#xff0c;因此特别适合用作网络设备发现。 我们可以自定义一个通信端口&#xff0c;假设为55555。我们再制定一个协议用于查询目标设备&#x…

2024-04-07 作业

作业要求&#xff1a; 1> 思维导图 2> 自由发挥应用场景实现一个登录窗口界面。 【可以是QQ登录界面、也可以是自己发挥的登录界面】 要求&#xff1a;尽量每行代码都有注释 作业1&#xff1a; 作业2&#xff1a; 运行代码&#xff1a; #include "myqwidget.h&quo…

hatch,现代化的 Python 项目管理和打包工具!

目录 前言 安装 特性 基本功能 项目创建 示例代码 虚拟环境管理 依赖管理 测试 打包和发布 高级功能 插件系统 配置环境管理 自定义构建选项 集成测试工具 实际应用场景 多环境管理 持续集成与持续部署&#xff08;CI/CD&#xff09; 项目原型化 依赖与包管理 总结 前言…

Q1剧集市场复盘:2024爱优腾谁在领跑国产剧市场?

2024年Q1剧集市场的成绩单出炉了。 复盘2024年第一季度剧集市场&#xff0c;可以用“生机勃勃”四个字来形容&#xff0c;虽然和去年相比&#xff0c;今年的第一季度缺少了《狂飙》这样的头部大爆款&#xff0c;但市场大盘走势向好。 根据灯塔专业版统计&#xff0c;2024Q1剧…

4.文件上传下载

一、配置文件 Spring#上传文件使用servlet:multipart:#单个文件最大上传大小max-file-size: 10MB#每次请求上传文件大小最大值max-request-size: 30MB #自定义参数 define:nginx:path: D:\uploadFile\ 二、service层 public interface FileService {void saveFile(byte[] f…

nginx配置实例-反向代理

目录 一、目标-反向代理实现效果 二、安装tomcat 三、配置nginx服务 四、配置反向代理 一、目标-反向代理实现效果 访问过程分析&#xff1a; 二、安装tomcat 1、安装jdk环境 新建/export/server目录 解压jdk 查看是否解压成功 配置jdk软连接 进入jdk的bin目录中&#x…

echart 折线图或散点图当横坐标为小数位时,若想显示整数该如何处理?

如图当前是这样的&#xff1a; 横坐标刻度目前是小数位&#xff0c;如果直接将小数位取整则会失去精度&#xff0c;所以我们要做的是刻度即是整数&#xff0c;又能显示小数位对应的数值&#xff1b; 思路就是直接手动设置刻度&#xff1a;设置xAxis的min,max,splitNumber,同时不…

next_permutation(下一个排列)问题

模板 从后往前找到第一次降序的位置&#xff0c;将这个点换成比之前大的最小数&#xff0c;将后面的数字转变为尽量小&#xff08;转化为升序&#xff09;。 下一个排列&#xff08;LeetCode31&#xff09; class Solution { public int nextGreaterElement(int n) { Stri…

蓝桥杯-冶炼金属(二分求最大最小)

P9240 [蓝桥杯 2023 省 B] 冶炼金属 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 二分做法&#xff1a; #include<bits/stdc.h> using namespace std; #define int long long const int N 1e410; int n,a,b; int v[N],cnt[N]; int check(int x){for(int i1;i<n;i…

火山方舟大模型服务平台调用Demo测试(豆包)

豆包得后台大模型支持为字节得火山方舟&#xff0c;所以想使用豆包的API&#xff0c;直接从这里就可以。 一、首先注册账号&#xff1a; 火山引擎-云上增长新动力 注册完成之后&#xff0c;控制台-账户-API访问密钥 二、找到API测试用例&#xff1a; Skylark-chat API调用…

数组排序(Comparator)

题目 import java.util.Arrays; import java.util.Comparator; import java.util.Scanner; public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt();sc.nextLine();Integer[] res new Integer[n1];//使用Integ…

让智能体像孩子一样观察别人学习动作,跨视角技能学习数据集EgoExoLearn来了

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了免费的人工智能中文站https://ai.weoknow.com 新建了收费的人工智能中文站https://ai.hzytsoft.cn/ 更多资源欢迎关注 在探索人工智能边界时&#xff0c;我们时常惊叹于人类孩童的学习能力 —— 可以轻易地将他人…