vue3-core/packages/reactivity/src/computed.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

169 lines
4.7 KiB
TypeScript
Raw Normal View History

import { type DebuggerOptions, ReactiveEffect } from './effect'
import { type Ref, trackRefValue, triggerRefValue } from './ref'
import { NOOP, hasChanged, isFunction } from '@vue/shared'
import { toRaw } from './reactive'
2021-07-08 02:37:28 +08:00
import type { Dep } from './dep'
import { DirtyLevels, ReactiveFlags } from './constants'
import { warn } from './warning'
2018-09-19 23:35:38 +08:00
declare const ComputedRefSymbol: unique symbol
export interface ComputedRef<T = any> extends WritableComputedRef<T> {
readonly value: T
[ComputedRefSymbol]: true
2018-09-19 23:35:38 +08:00
}
2019-10-05 22:10:37 +08:00
export interface WritableComputedRef<T> extends Ref<T> {
readonly effect: ReactiveEffect<T>
}
export type ComputedGetter<T> = (oldValue?: T) => T
export type ComputedSetter<T> = (newValue: T) => void
2019-10-22 01:57:20 +08:00
export interface WritableComputedOptions<T> {
2019-10-22 01:57:20 +08:00
get: ComputedGetter<T>
set: ComputedSetter<T>
}
export const COMPUTED_SIDE_EFFECT_WARN =
`Computed is still dirty after getter evaluation,` +
` likely because a computed is mutating its own dependency in its getter.` +
` State mutations in computed getters should be avoided. ` +
` Check the docs for more details: https://vuejs.org/guide/essentials/computed.html#getters-should-be-side-effect-free`
export class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
public readonly effect: ReactiveEffect<T>
2021-07-20 06:24:18 +08:00
public readonly __v_isRef = true
public readonly [ReactiveFlags.IS_READONLY]: boolean = false
public _cacheable: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
isSSR: boolean,
) {
this.effect = new ReactiveEffect(
() => getter(this._value),
() =>
triggerRefValue(
this,
this.effect._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect
? DirtyLevels.MaybeDirty_ComputedSideEffect
: DirtyLevels.MaybeDirty,
),
)
this.effect.computed = this
this.effect.active = this._cacheable = !isSSR
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
const self = toRaw(this)
if (
(!self._cacheable || self.effect.dirty) &&
hasChanged(self._value, (self._value = self.effect.run()!))
) {
triggerRefValue(self, DirtyLevels.Dirty)
}
trackRefValue(self)
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {
__DEV__ && warn(COMPUTED_SIDE_EFFECT_WARN)
triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)
}
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
// #region polyfill _dirty for backward compatibility third party code for Vue <= 3.3.x
get _dirty() {
return this.effect.dirty
}
set _dirty(v) {
this.effect.dirty = v
}
// #endregion
}
/**
* Takes a getter function and returns a readonly reactive ref object for the
* returned value from the getter. It can also take an object with get and set
* functions to create a writable ref object.
*
* @example
* ```js
* // Creating a readonly computed ref:
* const count = ref(1)
* const plusOne = computed(() => count.value + 1)
*
* console.log(plusOne.value) // 2
* plusOne.value++ // error
* ```
*
* ```js
* // Creating a writable computed ref:
* const count = ref(1)
* const plusOne = computed({
* get: () => count.value + 1,
* set: (val) => {
* count.value = val - 1
* }
* })
*
* plusOne.value = 1
* console.log(count.value) // 0
* ```
*
* @param getter - Function that produces the next value.
* @param debugOptions - For debugging. See {@link https://vuejs.org/guide/extras/reactivity-in-depth.html#computed-debugging}.
* @see {@link https://vuejs.org/api/reactivity-core.html#computed}
*/
2019-06-06 12:35:49 +08:00
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,
isSSR = false,
2019-10-22 23:26:48 +08:00
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
getter = getterOrOptions
setter = __DEV__
? () => {
warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
if (__DEV__ && debugOptions && !isSSR) {
cRef.effect.onTrack = debugOptions.onTrack
cRef.effect.onTrigger = debugOptions.onTrigger
}
return cRef as any
2018-09-19 23:35:38 +08:00
}