Vue3源码reactivity响应式篇之computed计算属性

概述

vue3中,computed函数用于表示计算属性,有惰性求值、响应式追踪依赖的特点。本文将介绍computed的实现原理以及其机制细节。

源码解析

computed计算属性和computed方法、ComputedRefImpl类以及refreshComputed方法有关。

computed方法

computed暴露给外部的就是computed方法,其源码实现如下:

js 复制代码
function computed(getterOrOptions, debugOptions, isSSR = false) {
  let getter;
  let setter;
  if (shared.isFunction(getterOrOptions)) {
    getter = getterOrOptions;
  } else {
    getter = getterOrOptions.get;
    setter = getterOrOptions.set;
  }
  const cRef = new ComputedRefImpl(getter, setter, isSSR);
  return cRef;
}

computed方法实现比较简单,需要关注参数getterOrOptionsisSSRisSSR默认为false,它在服务端渲染会传值为truedebugOptions用以在开发环境调试。

computed会先判断getterOrOptions是否是函数,若是函数,则将其赋值给getter;当然getterOptions也可以是一个包含getset方法的对象。computed方法返回的是ComputedRefImpl实例,一般我们读取计算属性的值也是读取它的返回值的.value

ComputedRefImpl

ComputedRefImpl用于构造一个计算属性。

ComputedRefImpl的源码实现如下:

js 复制代码
class ComputedRefImpl {
    constructor(fn, setter, isSSR) {
        this.fn = getter; //计算函数
        this.setter = setter; // 设置函数(可选)
        this["_value"] = undefined; // 缓存的结果,计算属性的值
        this.dep = new Dep(this); // 依赖收集器(收集依赖此计算属性的副作用effect)
        this["__v_isRef"] = true; // 表示为ref类型
        this["__v_isReadonly"] = undefined; // 只读标记
        this.deps = undefined; //当前计算属性依赖的响应式集合对象链表头
        this.depsTail = undefined; //链表尾
        this.flags = 16; //状态标记
        this.globalVersion = globalVersion - 1;// 全局版本号,用于脏检查
        this.isSSR = undefined; //服务端渲染标记
        this.next = undefined; //用于在effect链表中指向下一个节点
        this.effect = this; // 指向自身
        this["__V_isReadonly"] = !setter; //若无setter,则表示计算属性是只读的
        this.isSSR = isSSR;//ssr标记赋值
    }
    // 依赖变更调用
    notify() {
        this.flags |= 16;
        if (!(this.flags & 8) && activeSub !== this) {
            batch(this, true);
            return true;
        }
    }
    // 计算属性值访问器
    get value() {
        const link = this.dep.track()
        refreshComputed(this);
        if (link) {
            link.version = this.dep.version;
        }
        return this._value;
    }
    // 计算属性值设置器
    set value(newValue) {
        if (this.setter) {
            this.setter(newValue)
        } else {
            warn("Write operation failed: computed value is readonly");
        }
    }
}

ComputedRefImpl中除了在构造器中定义了相关属性外,就是包含两个属性访问器函数和一个notify方法

notify

当计算属性依赖的响应式值发生变化时,会调用notify方法.notify方法会先设置this.flags标志位的值,将其第4位置为1 ,表示有更新请求;然后判断标志位的第3位是否为1 并且当前激活订阅(副作用)是不是自身,若条件满足,则调用batch方法,将该计算属性添加到更新队列中,进行批量更新,最后返回true,表示更新已调度;若不满足条件,则返回false,表示更新被跳过。

getter

getter属性访问器,会在读取计算属性的值时触发。先进行依赖收集,追踪当前正在运行的effect,然后调用refreshComputed方法进行有条件性的重新计算,若当前计算属性被其他effect依赖,则更新依赖的版本,最后返回this._value

setter

setter属性设置,一般情况下计算属性只是只读的,若this.setter方法存在,则可以调用该方法设置计算属性的值。

refreshComputed方法

refreshComputed方法就是用于进行刷新计算属性的值,满足条件就重新进行计算,得到最新的计算属性的值。

refreshComputed的源码实现如下:

js 复制代码
function refreshComputed(computed) {
    // 检查更新条件
    if (computed.flags & 4 && !(computed.flags & 16)) {
        return;
    }
    // 清除pending状态
    computed.flags & =-17;

    // 全局版本校验 避免重复计算
    if (computed.globalVersion === globalVersion) {
        return;
    }
    computed.globalVersion = globalVersion;

    // 缓存有效性检查 
    if (!computed.isSSR && computed.flags & 128 && (!computed.deps && !computed._dirty || !isDirty(computed))) {
        return;
    }

    // 标记计算状态
    computed.flags |= 2;
    const dep = computed.dep;
    const prevSub = activeSub;
    const prevShouldTrack = shouldTrack;
    activeSub = computed;
    shouldTrack = true;
    try {
       // 依赖收集准备
        prepareDeps(computed);

        // 执行计算函数
        const value = computed.fn(computed._value);
        
        // 值变化检测
        if (dep.version === 0 || hasChanged(value, computed._value)) {
            computed.flags |= 128; // 标记valid
            computed._value = value;  
            dep.version++; // 触发依赖更新
        }
    } catch (err) {
        dep.version++;
        throw err;
    } finally {
        activeSub = prevSub;
        shouldTrack = prevShouldTrack;
        cleanupDep(computed); // 清理过期依赖
        computed.flags &= -3; // 清除computing状态
    }
}

refreshComputed方法会先检查flags的值,若是被移除或者没有更新请求,则直接返回;然后修改flags状态,清除pending状态;比较全局版本号和计算属性的版本号,若二者一样,则返回,避免是在计算属性中修改了响应式属性引起的重新计算;修改响应式的版本号;然后做缓存有效性的检查;若是脏数据,则返回;再次标记flags状态,表示是计算中;将当前effect计算属性切换为activeSub,修改shouldTracktrue,调用prepareDeps进行依赖收集,然后执行计算属性的fn,即传入computed的参数函数,得到新的value,比较计算属性的值是否发生改变,赋值this._value,并将其依赖dep的版本递增,如此会触发依赖该计算属性的副作用effect更新;最后恢复activeSubshouldTrack,清理过期依赖以及清除计算状态。

refreshComputed是计算属性computed的核心方法,依据一些规则判断需要执行fn,获取最新的value以及触发相关依赖。

相关推荐
向下的大树1 小时前
npm 最新镜像,命令导致下载错误
前端·npm·node.js
宁雨桥1 小时前
Service Worker:前端离线化与性能优化的核心技术
前端·性能优化
IT_陈寒1 小时前
SpringBoot实战:这5个隐藏技巧让我开发效率提升200%,90%的人都不知道!
前端·人工智能·后端
ObjectX前端实验室1 小时前
【图形编辑器架构】节点树与渲染树的双向绑定原理
前端·计算机图形学·图形学
excel2 小时前
Vue2 与 Vue3 生命周期详解与对比
前端
一只猪皮怪53 小时前
React 18 前端最佳实践技术栈清单(2025版)
前端·react.js·前端框架
Misnice3 小时前
React渲染超大的字符串
前端·javascript·react.js
天天向上的鹿茸3 小时前
用矩阵实现元素绕不定点旋转
前端·线性代数·矩阵
李鸿耀6 小时前
主题换肤指南:设计到开发的完整实践
前端
带娃的IT创业者11 小时前
TypeScript + React + Ant Design 前端架构入门:搭建一个 Flask 个人博客前端
前端·react.js·typescript