diff --git a/packages/shared/computedWithControl/index.md b/packages/shared/computedWithControl/index.md index e4feceafbbe..1af150c1736 100644 --- a/packages/shared/computedWithControl/index.md +++ b/packages/shared/computedWithControl/index.md @@ -5,7 +5,7 @@ alias: controlledComputed # computedWithControl -Explicitly define the deps of computed. +Explicitly define the dependencies of computed. ## Usage @@ -34,3 +34,16 @@ source.value = 'bar' console.log(computedRef.value) // 1 ``` + +### Manual Triggering + +You can also manually trigger the update of the computed by: + +```ts +const computedRef = computedWithControl( + () => source.value, + () => counter.value, +) + +computedRef.trigger() +``` diff --git a/packages/shared/computedWithControl/index.test.ts b/packages/shared/computedWithControl/index.test.ts index e53e60e2301..b0aaf86a8d5 100644 --- a/packages/shared/computedWithControl/index.test.ts +++ b/packages/shared/computedWithControl/index.test.ts @@ -18,4 +18,47 @@ describe('computedWithControl', () => { expect(computed.value).toBe('BAR') }) + + it('custom trigger', () => { + let count = 0 + const computed = computedWithControl(() => {}, () => count) + + expect(computed.value).toBe(0) + + count += 1 + + expect(computed.value).toBe(0) + + computed.trigger() + + expect(computed.value).toBe(1) + }) + + it('getter and setter', () => { + const trigger = ref(0) + const data = ref('foo') + + const computed = computedWithControl(trigger, { + get() { + return data.value.toUpperCase() + }, + set(v) { + data.value = v + }, + }) + + expect(computed.value).toBe('FOO') + + data.value = 'bar' + + expect(computed.value).toBe('FOO') + + trigger.value += 1 + + expect(computed.value).toBe('BAR') + + computed.value = 'BAZ' + + expect(data.value).toBe('BAZ') + }) }) diff --git a/packages/shared/computedWithControl/index.ts b/packages/shared/computedWithControl/index.ts index fd45ad038a1..afa875421ad 100644 --- a/packages/shared/computedWithControl/index.ts +++ b/packages/shared/computedWithControl/index.ts @@ -1,6 +1,27 @@ -import type { ComputedRef, WatchSource } from 'vue-demi' +import type { ComputedGetter, ComputedRef, WatchSource, WritableComputedOptions, WritableComputedRef } from 'vue-demi' import { customRef, ref, watch } from 'vue-demi' import type { Fn } from '../utils' +import { isFunction } from '../utils' + +export interface ComputedWithControlRefExtra { + /** + * Force update the computed value. + */ + trigger(): void +} + +export interface ComputedRefWithControl extends ComputedRef, ComputedWithControlRefExtra {} +export interface WritableComputedRefWithControl extends WritableComputedRef, ComputedWithControlRefExtra {} + +export function computedWithControl( + source: WatchSource | WatchSource[], + fn: ComputedGetter +): ComputedRefWithControl + +export function computedWithControl( + source: WatchSource | WatchSource[], + fn: WritableComputedOptions +): WritableComputedRefWithControl /** * Explicitly define the deps of computed. @@ -8,37 +29,46 @@ import type { Fn } from '../utils' * @param source * @param fn */ -export function computedWithControl(source: WatchSource | WatchSource[], fn: () => T) { +export function computedWithControl( + source: WatchSource | WatchSource[], + fn: ComputedGetter | WritableComputedOptions, +) { let v: T = undefined! let track: Fn let trigger: Fn const dirty = ref(true) - watch( - source, - () => { - dirty.value = true - trigger() - }, - { flush: 'sync' }, - ) + const update = () => { + dirty.value = true + trigger() + } - return customRef((_track, _trigger) => { + watch(source, update, { flush: 'sync' }) + + const get = isFunction(fn) ? fn : fn.get + const set = isFunction(fn) ? undefined : fn.set + + const result = customRef((_track, _trigger) => { track = _track trigger = _trigger return { get() { if (dirty.value) { - v = fn() + v = get() dirty.value = false } track() return v }, - set() {}, + set(v) { + set?.(v) + }, } - }) as ComputedRef + }) as ComputedRefWithControl + + result.trigger = update + return result } // alias