Vue3源码reactivity响应式篇之computed计算属性
概述
vue3中,computed
函数用于表示计算属性,有惰性求值、响应式追踪依赖的特点。本文将介绍computed
的实现原理以及其机制细节。
源码解析
computed
计算属性和computed
方法、ComputedRefImpl
类以及refreshComputed
方法有关。
computed
方法
computed
暴露给外部的就是computed
方法,其源码实现如下:
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
方法实现比较简单,需要关注参数getterOrOptions
和isSSR
,isSSR
默认为false
,它在服务端渲染会传值为true
。debugOptions
用以在开发环境调试。
computed
会先判断getterOrOptions
是否是函数,若是函数,则将其赋值给getter
;当然getterOptions
也可以是一个包含get
和set
方法的对象。computed
方法返回的是ComputedRefImpl
实例,一般我们读取计算属性的值也是读取它的返回值的.value
。
ComputedRefImpl
类
ComputedRefImpl
用于构造一个计算属性。
ComputedRefImpl
的源码实现如下:
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
的源码实现如下:
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; // 标记validcomputed._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
,修改shouldTrack
为true
,调用prepareDeps
进行依赖收集,然后执行计算属性的fn
,即传入computed
的参数函数,得到新的value
,比较计算属性的值是否发生改变,赋值this._value
,并将其依赖dep
的版本递增,如此会触发依赖该计算属性的副作用effect
更新;最后恢复activeSub
和shouldTrack
,清理过期依赖以及清除计算状态。
refreshComputed
是计算属性computed
的核心方法,依据一些规则判断需要执行fn
,获取最新的value
以及触发相关依赖。