computed的基本使用
computed
是组件的计算属性,它的含义是依赖于其他状态而生成的状态,与响应式紧密相关。
computed
有两种创建方式:
-
给
computed
函数传递一个getter
方法创建immutable reactive ref object
const count = ref(1) const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2 plusOne.value = 3 // error,因为plusOne是immutable ref obj
-
给
computed
函数传递一个有get
和set
方法的对象来创建一个 writable ref objectconst count = ref(1) const plusOne = computed({ get: () => count.value + 1, set: val => { count.value = val - 1 } }) plusOne.value = 1 console.log(count.value) // 0
computed原理
computed
的特性就在于能够缓存计算的值(提升性能),只有当 computed
的依赖发生变化时才会重新计算,否则读取 computed
的值则一直是之前的值。computed
是怎么做到的呢?让我们一起来看看源码来解惑。
computed源码
源码地址:packages\reactivity\src\computed.ts
computed创建
computed
函数接收一个 getter
方法或者是一个含有 get
方法和 set
方法的对象,并返回一个 ref
对象。
export function computed<T>(
getter: ComputedGetter<T>,
debugOptions?: DebuggerOptions
): ComputedRef<T>
export function computed<T>(
options: WritableComputedOptions<T>,
debugOptions?: DebuggerOptions
): WritableComputedRef<T>
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
const onlyGetter = isFunction(getterOrOptions)
// 当getterOrOptions为函数的时候,说明是只读computed,会将其赋值给与getter
if (onlyGetter) {
getter = getterOrOptions
setter = __DEV__
? () => {
console.warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
// 当getterOrOptions为对象的时候,说明是是自定义的getter setter,会将set和get分别赋值给setter,getter。
getter = getterOrOptions.get
setter = getterOrOptions.set
}
const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter)
if (__DEV__ && debugOptions) {
cRef.effect.onTrack = debugOptions.onTrack
cRef.effect.onTrigger = debugOptions.onTrigger
}
return cRef as any
}
ComputedRefImpl产生ref对象
通过上面的代码我们可以看出computed
函数返回的ref对象是执行构造方法 ComputedRefImpl
而创建的一个实例。 ComputedRefImpl
的构造方法一共做了两件事:
- 调用
effect
方法生成watcher
监听函数并赋值给实例的 effect 属性。 - 设置ref对象是否为
readonly
。
class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
private _dirty = true // 脏数据flag 用来判断是否需要重新计算
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true // ref响应式对象标识
public readonly [ReactiveFlags.IS_READONLY]: boolean // ReactiveFlags只读标识
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
// 调用 ReactiveEffect 方法生成监听函数并赋值给实例的 effect 属性
this.effect = new ReactiveEffect(getter, () => {
// 由于初始化时,计算过一次computed值,_dirty已经设为了false
// 所以当内部依赖发生变化时,会由此进入,设置_dirty为true,这样获取computed值时会再次计算
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
})
// 设置ref对象是否为 readonly
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
const self = toRaw(this)
trackRefValue(self) // 依赖收集
// 初始化时,_dirty为true,会计算一次computed值
if (self._dirty) {
// 设置_dirty为false, 防止再次获取时重新计算,这就是 computed 缓存值的实现原理
self._dirty = false
self._value = self.effect.run()!
}
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
computed
缓存值的实现分析
我们发现声明一个 computed
时其实并不会执行getter方法,只有在读取 computed
值时才会执行它的 getter
方法,而这个方法实在构造函数 ComputedRefImpl
的 getter
方法定义实现的。
getter
方法会在读取 computed
值的时候执行(包含依赖收集),由于脏数据的开关,初始化时 _dirty
被设为 true
) ,在 getter
方法中会计算一遍computed
的值并设置self._dirty = false
,在数据源不发生变化的情况下之后再获取 computed
的值时由于 _dirty
是 false
就不会重新计算。这就是 computed
缓存值的实现原理。
computed重新计算值
在数据源发生变化时,在ComputedRefImpl
构造函数里为对象添加的effect
函数会给对象的响应式对象生成监听函数,并对 scheduler
进行了设置。
所以,当computed
内部依赖的状态发生改变,执行对应的监听函数,这其中自然会执行 scheduler
里的操作。而在 scheduler
中将 _dirty
设为了 true
。从而在下次取值时,进行重新计算。
结语
如果本文对你有一丁点帮助,点个赞支持一下吧,感谢感谢