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以及触发相关依赖。

相关推荐
落日沉溺于海3 小时前
React From表单使用Formik和yup进行校验
开发语言·前端·javascript
知识分享小能手3 小时前
React学习教程,从入门到精通, React 新创建组件语法知识点及案例代码(11)
前端·javascript·学习·react.js·架构·前端框架·react
会豪3 小时前
工业仿真(simulation)--前端(五)--标尺,刻度尺
前端
会豪3 小时前
工业仿真(simulation)--前端(四)--画布编辑(2)
前端
an__ya__3 小时前
Vue数据响应式reactive
前端·javascript·vue.js
苦逼的搬砖工3 小时前
Flutter UI Components:闲来无事,设计整理了这几年来使用的UI组件库
前端·flutter
想买Rolex和Supra的凯美瑞车主3 小时前
Taro + Vite 开发中 fs.allow 配置问题分析与解决
前端
ruanCat3 小时前
使用 vite 的 base 命令行参数来解决项目部署在 github page 的路径问题
前端·github
Codebee3 小时前
使用Qoder 改造前端UI/UE升级改造实践:从传统界面到现代化体验的华丽蜕变
前端·人工智能