【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 = true
    public readonly [ReactiveFlags.IS_READONLY]: boolean = false

    public _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 = this
        this.effect.active = this._cacheable = !isSSR
        this[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.x
    get _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 = getterOrOptions
        setter = __DEV__
            ? () => {
                warn('Write operation failed: computed value is readonly')
            }
            : NOOP
    } else {
        getter = getterOrOptions.get
        setter = getterOrOptions.set
    }

    //使用 ComputedRefImpl 类来实际创建计算属性
    const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)

    if (__DEV__ && debugOptions && !isSSR) {
        cRef.effect.onTrack = debugOptions.onTrack
        cRef.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 提供的响应式功能构建高效的应用。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯-dfs搜索模板题(一)

蓝桥杯-dfs搜索模板题&#xff08;一&#xff09; P2089 烤鸡P1088 火星人P1149 火柴棒等式P2036 PERKETP1135 奇怪的电梯结语 P2089 烤鸡 对于每个位置枚举数字 #include<bits/stdc.h>using namespace std;const int N1010;int n;int arr[N];//临时方案 int res0;//方案…

【闲聊】-网页划词翻译插件

英文之痛 作为程序猿&#xff0c;常常需要接触外文网站&#xff0c;以前很痛苦&#xff0c;现在大模型时代有很多智能工具可以直接翻译&#xff0c;翻译的虽然越来越好&#xff0c;但是还是不如直接看英文能理解本义&#xff0c;相信我&#xff0c;看翻译的理解和看原文的理解…

AJAX —— 学习(二)

目录 一、利用 JSON 字符串 返回数据 &#xff08;一&#xff09;基础代码 &#xff08;二&#xff09;原理及实现 二、nodmon 工具 自动重启服务 &#xff08;一&#xff09;用途 &#xff08;二&#xff09;下载 &#xff08;三&#xff09;使用 三、IE 缓存问题 &a…

开源模型应用落地-chatglm3-6b模型小试-入门篇(一)

一、前言 刚开始接触AI时&#xff0c;您可能会感到困惑&#xff0c;因为面对众多开源模型的选择&#xff0c;不知道应该选择哪个模型&#xff0c;也不知道如何调用最基本的模型。但是不用担心&#xff0c;我将陪伴您一起逐步入门&#xff0c;解决这些问题。 在信息时代&#xf…

ADB 命令之 模拟按键/输入

ADB 命令之 模拟按键/输入 模拟按键/输入 在 ​​adb shell​​​ 里有个很实用的命令叫 ​​input​​&#xff0c;通过它可以做一些有趣的事情。 ​​input​​ 命令的完整 help 信息如下&#xff1a; Usage: input [<source>] <command> [<arg>...] Th…

MySQL 中将使用逗号分隔的字段转换为多行数据

在我们的实际开发中&#xff0c;经常需要存储一些字段&#xff0c;它们使用像, - 等连接符进行连接。在查询过程中&#xff0c;有时需要将这些字段使用连接符分割&#xff0c;然后查询多条数据。今天&#xff0c;我们将使用一个实际的生产场景来详细解释这个解决方案。 场景介绍…

数据结构——红黑树详解

一、红黑树的定义 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长出两倍&#xff0c…

转专业:集成电路、微电子、电子信息选哪个?

目录 集成电路专业 微电子技术专业 电子信息工程专业 综合分析 在考虑转专业到集成电路、微电子或电子信息时&#xff0c;您需要考虑多个因素&#xff0c;包括个人兴趣、专业课程内容、行业前景以及未来就业市场的需求。以下是关于这三个专业的详细分析&#xff0c;以及它们…

酷开科技智慧AI让酷开系统大显身手!

时代的浪潮汹涌而至&#xff0c;人工智能作为技术革新和产业变革的重要引擎&#xff0c;正深刻地影响着各行各业。在科技的海洋中&#xff0c;AI技术正逐渐渗透到我们的日常生活中&#xff0c;为我们带来前所未有的便捷和智慧。酷开科技用技术探索智慧AI&#xff0c;别看它只是…

让六西格玛培训有效的三个步骤,拿走不谢!

近年来&#xff0c;六西格玛作为一种先进的质量管理方法&#xff0c;被众多企业视为提升产品质量、优化流程、减少浪费的利器。然而&#xff0c;如何使六西格玛培训真正落地生根&#xff0c;发挥出其应有的效果&#xff0c;成为了许多企业关注的焦点。本文&#xff0c;天行健Si…

Java零基础入门到精通_Day 4

方法的重载 就是同一个类中的相同方法名的多个方法&#xff0c;但是他们的参数不同&#xff0c;类型不同或者参数个数不同。 &#xff08;与返回值无关&#xff09; package Base_One;public class Base_005 {public static void main(String[] args) {// Main method logic …

探析Drools规则引擎的工作机制

目录 一、工作原理 二、工作流程 2.1 初始化环境 2.2 添加规则文件 2.3 编译规则文件 2.4 插入到工作内存 2.5 规则匹配与激活 2.6 规则执行 三、Drools 其他特性 3.1 符合事实 3.2 决策表 3.3 规则生命周期管理 3.4 规则流 四、Rete 算法 一、工作原理 Drools 规则引擎的工…

如何理解Java注解反射

Java 8 中文版 - 在线API手册 - 码工具 什么是注解 ◆Annotation是从JDK5.0开始引入的新技术 ◆Annotation的作用: 不是程序本身&#xff0c;可以对程序作出解释.(这一点和注释(comment)没什么区别) 可以被其他程序(比如:编译器等)读取 Annotation的格式: > 注解是以&quo…

OSError: Can‘t load tokenizer for ‘bert-base-chinese‘

文章目录 OSError: Cant load tokenizer for bert-base-chinese1.问题描述2.解决办法 OSError: Can’t load tokenizer for ‘bert-base-chinese’ 1.问题描述 使用from_pretrained()函数从预训练的权重中加载模型时报错&#xff1a; OSError: Can’t load tokenizer for ‘…

数据结构栈和堆列

目录 栈&#xff1a; 栈的概念&#xff1a; 栈的实现&#xff1a; 栈接口的实现&#xff1a; 1.初始化栈&#xff1a; 2.入栈&#xff1a; 3.出栈&#xff1a; 4. 获取栈顶元素&#xff1a; 5.获取栈中有效数据的个数&#xff1a; 6.检测栈是否为空&#xff0c;如果为…

搜索二维矩阵 II - LeetCode 热题 21

大家好&#xff01;我是曾续缘&#x1f497; 今天是《LeetCode 热题 100》系列 发车第 21 天 矩阵第 4 题 ❤️点赞 &#x1f44d; 收藏 ⭐再看&#xff0c;养成习惯 搜索二维矩阵 II 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&…

20240402—Qt如何通过动态属性设置按钮样式?

前言 正文 1、点击UI文件 2、选择Bool型或是QString 3、设置后这里出现动态属性 4、这qss文件中绑定该动态属性 QPushButton[PopBlueBtn"PopBlueBtn"]{background-color:#1050B7;color:#FFFFFF;font-size:20px;font-family:Source Han Sans CN;//思源黑体 CNbor…

Ansys Zemax | 如何将光栅数据从Lumerical导入至OpticStudio(上)

附件下载 联系工作人员获取附件 本文介绍了一种使用Ansys Zemax OpticStudio和Lumerical RCWA在整个光学系统中精确仿真1D/2D光栅的静态工作流程。将首先简要介绍方法。然后解释有关如何建立系统的详细信息。 本篇内容将分为上下两部分&#xff0c;上部将首先简要介绍方法工…

01 Python进阶:正则表达式

re.match函数 使用 Python 中的 re 模块时&#xff0c;可以通过 re.match() 函数来尝试从字符串的开头匹配一个模式。以下是一个简单的详解和举例&#xff1a; import re# 定义一个正则表达式模式 pattern r^[a-z] # 匹配开头的小写字母序列# 要匹配的字符串 text "h…

程序的编译、链接过程分析(简洁浓缩版)!

《嵌入式工程师自我修养/C语言》系列——程序的编译、链接过程分析&#xff08;简洁浓缩版&#xff09;&#xff01; 一、程序的编译1.1 预编译指令 pragma1.2 编译过程概述1.3 符号表和重定位表 二、程序的链接2.1 分段组装2.2 符号决议2.2.1 强符号与弱符号2.2.2 GNU编译器的…