Skip to content

Commit

Permalink
feat(computedWithControl): support manual triggering
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Jul 6, 2022
1 parent 80570f7 commit 01fca3b
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 15 deletions.
15 changes: 14 additions & 1 deletion packages/shared/computedWithControl/index.md
Expand Up @@ -5,7 +5,7 @@ alias: controlledComputed

# computedWithControl

Explicitly define the deps of computed.
Explicitly define the dependencies of computed.

## Usage

Expand Down Expand Up @@ -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()
```
43 changes: 43 additions & 0 deletions packages/shared/computedWithControl/index.test.ts
Expand Up @@ -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')
})
})
58 changes: 44 additions & 14 deletions packages/shared/computedWithControl/index.ts
@@ -1,44 +1,74 @@
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<T> extends ComputedRef<T>, ComputedWithControlRefExtra {}
export interface WritableComputedRefWithControl<T> extends WritableComputedRef<T>, ComputedWithControlRefExtra {}

export function computedWithControl<T, S>(
source: WatchSource<S> | WatchSource<S>[],
fn: ComputedGetter<T>
): ComputedRefWithControl<T>

export function computedWithControl<T, S>(
source: WatchSource<S> | WatchSource<S>[],
fn: WritableComputedOptions<T>
): WritableComputedRefWithControl<T>

/**
* Explicitly define the deps of computed.
*
* @param source
* @param fn
*/
export function computedWithControl<T, S>(source: WatchSource<S> | WatchSource<S>[], fn: () => T) {
export function computedWithControl<T, S>(
source: WatchSource<S> | WatchSource<S>[],
fn: ComputedGetter<T> | WritableComputedOptions<T>,
) {
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<T>((_track, _trigger) => {
watch(source, update, { flush: 'sync' })

const get = isFunction(fn) ? fn : fn.get
const set = isFunction(fn) ? undefined : fn.set

const result = customRef<T>((_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<T>
}) as ComputedRefWithControl<T>

result.trigger = update
return result
}

// alias
Expand Down

0 comments on commit 01fca3b

Please sign in to comment.