computed
一、认识
computed
侦听依赖的响应式数据, 根据传入类型, 生成不同的 Ref
类型的响应式数据。
-
computed(()=>{})
: 传入getter
函数, 返回一个只读的响应式ref
对象 -
computed({ get(){}, set(){} })
: 返回一个可读、可写的响应式ref
对象
二、语法
computed(()=>{},debuggerOptions?);
computed(
{
get(){
},
set(value){
}
},
debuggerOptions?:
);
三、用法
3.1 可读计算属性
import { ref, type Ref, computed } from 'vue'
const a: Ref<number> = ref(0)
const count = computed(() => {
return a.value * 10
})
3.2 可读可写计算属性
import { ref, type Ref, computed } from 'vue'
const a: Ref<number> = ref(0)
const count = computed({
get() {
return a.value * 10
},
set(value) {
a.value = value
}
})
3.3 收集、管理副作用
import { ref,stop,computed } from 'vue';
let disposables = [];
const count = ref(0);
const doubleCount = computed(()=> count.value * 2);
disposables.push(()=> stop(doubleCount));
setTimeout(()=>{
count.value++;
},2000);
setTimeout(()=>{
disposables.forEach(dispose=> dispose());
disposables = []
},4000);
setTimeout(()=>{
count.value++;
}, 60000);
四、对比
4.1 compute 实现原理
Vue.js 3.0
中, computed
是一个计算属性, 根据传入的 getter
函数, 计算得到一个可读或者可读可写的响应式数据。Computed
具有惰性计算、缓存更新等特点。在 Vue 3
中, Computed
的实现是建立在响应式系统和 ReactiveEffect
之上的一种缓存计算机制, 主要依赖于 ReactiveEffect
来包装计算函数, 并利用调度器和 dirty
标记以及 _value
实现惰性计算与缓存更新。这种设计既保证了 Computed
只在必要时重新计算,又能高效地追踪依赖,实现高性能的响应式数据更新,是 Vue 3
响应式系统的重要组成部分。
一、Computed
底层结构
class ComputedRefImpl {
constructor(getter, setter, isReadonly) {
this.getter = getter;
this.setter = setter;
this._value = undefined;
this.dirty = true;
this.effect = new ReactiveEffect(getter, () => {
if (!this.dirty) {
this.dirty = true;
triggerRefValue(this);
}
});
this.__v_isRef = true;
this[ReactiveFlags.IS_READONLY] = isReadonly;
}
get value() {
trackRefValue(this);
if (this.dirty) {
this._value = this.effect.run();
this.dirty = false;
}
return this._value;
}
set value(newVal) {
if (this.setter) {
this.setter(newVal);
} else {
console.warn('Write operation failed: computed value is readonly.');
}
}
}
二、属性方法:
-
_value
: 缓存计算后的结果。 -
dirty
: 一个布尔标记,指示当前缓存是否失效。当依赖变化时会被标记为true
,表示下次读取需要重新计算。 -
effect
: 内部创建的ReactiveEffect
, 用于运行计算函数,并通过scheduler
实现依赖变化时标记computed
为脏。 -
__v_isRef
: 标识这是一个ref
(computed
也算作ref
),使得computed
对象具有统一的访问接口(即通过.value
访问)。 -
value getter
: 当外部访问 computedRef.value 时,首先会检查 dirty 标记。如果为 true,则调用 effect.run() 重新计算,并更新 _value;否则直接返回 _value。 -
value setter
(可选):如果用户为computed
提供了setter
,则computedRef
实现setter
,调用用户自定义的更新函数;如果没有setter
,则在尝试修改时会给出警告或不允许修改。
三、惰性计算: Computed
内部创建的 ReactiveEffect
会把计算函数包裹进去, 同时传入一个调度器函数(scheduler``)。当计算函数中依赖的响应式数据更新时,scheduler
不会立即调用 effect.run()
重新计算,而是简单地将 computedRef
的 dirty
标记为 true
。这样,computed
是 惰性 的, 只有在下次读取 computedRef.value
时才会重新计算。
四、依赖追踪与缓存更新: 1. 缓存, 当 Computed
的 getter
被执行时, 其返回值被存储在 _value
中。如果在此后依赖没有变化, 则 _value
会直接复用,避免了重复计算。2. 依赖追踪, 当 Computed
内部依赖的响应式数据更新后(例如在 getter
内部通过 reactive
对象的 get
拦截器触发 track``),scheduler
会执行, 将 Computed
的 dirty
标记设为 true
,并通过 triggerRefValue
通知所有依赖该 computed
的 effect
(例如在模板中使用该 computed
的组件),使得它们在下一次访问 computed.value
时重新计算。
4.2 computed vs watch
Watch
是基于响应式系统构建的一个高层 API
,用于观察响应式数据的变化并在数据变化时触发回调。Watch
具体工作流如下: 1. 响应式依赖追踪, Watch
的本质是创建一个 ReactiveEffect
来观察 数据源(可以是 ref
、reactive
对象、getter
函数或它们的组合)。当数据源中依赖的数据发生变化时, ReactiveEffect
会触发调度器,进而执行 watch
回调。2. 懒执行与调度, Watch
通过传入自定义的调度器(scheduler
), 可以控制何时执行回调函数, 默认情况下, watch
会异步调度更新(比如在微任务队列中), 也支持同步(flush: 'sync'
)或 post-render(flush: 'post')
等不同的调度时机。3. 清理与失效, 为了处理异步回调中可能存在的竞争问题,watch
提供了 onInvalidate
回调,允许用户注册清理函数, 如果在异步任务期间依赖发生变化,则会调用该清理函数,以防止过时的回调继续执行。
Computed
是一个计算属性, 根据传入的 getter
函数, 计算得到一个可读或者可读可写的响应式数据。Computed
具有惰性计算、缓存更新等特点。在 Vue 3
中, Computed
的实现是建立在响应式系统和 ReactiveEffect
之上的一种缓存计算机制, 主要依赖于 ReactiveEffect
来包装计算函数, 并利用调度器和 dirty
标记以及 _value
实现惰性计算与缓存更新。这种设计既保证了 Computed
只在必要时重新计算,又能高效地追踪依赖,实现高性能的响应式数据更新,是 Vue 3
响应式系统的重要组成部分。
因此, Watch
和 Computed
依赖收集都是基于 ReactiveEffect
来实现的, 也都传入了调度器进行定制逻辑。但是, Computed
的 ReactiveEffect
包装的是传入的计算函数; Watch
中的 ReactiveEffect
包装的是观察 数据源(可以是 ref
、reactive
对象、getter
函数或它们的组合)的函数。
4.3 computed vs methods
methods
: 多次访问 methods
中的方法, 总是多次执行;
computed
是一个计算属性, 根据传入的 getter
函数, 计算得到一个可读或者可读可写的响应式数据。Computed
具有惰性计算、缓存更新等特点。在 Vue 3
中, Computed
的实现是建立在响应式系统和 ReactiveEffect
之上的一种缓存计算机制, 主要依赖于 ReactiveEffect
来包装计算函数, 并利用调度器和 dirty
标记以及 _value
实现惰性计算与缓存更新。这种设计既保证了 Computed
只在必要时重新计算,又能高效地追踪依赖,实现高性能的响应式数据更新,是 Vue 3
响应式系统的重要组成部分。