diff --git a/src/index.ts b/src/index.ts index 4f3c29d..641e9f1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { isRef, + triggerRef, computed as vueComputed, customRef as vueCustomRef, readonly as vueReadonly, @@ -40,12 +41,18 @@ function toFunctional(raw: any, readonly: boolean): any { ]) ) + const set = (value: any) => (raw.value = value) + const mutate = (mutator: (value: any) => any) => { + mutator(raw.value) + triggerRef(raw) + } + return new Proxy(fn, { ...handlers, get(target, key) { - if (!readonly && key === 'set') { - return (value: any) => (raw.value = value) - } else if (key === '__raw_ref') { + if (!readonly && key === 'set') return set + else if (!readonly && key === 'mutate') return mutate + else if (key === '__raw_ref') { return toRawRef(raw) } else { return Reflect.get(raw, key, raw) diff --git a/tests/index.test.ts b/tests/index.test.ts index 3976d44..eef27ce 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -5,6 +5,7 @@ import { customRef, isReadonly, isRef, + nextTick, reactive, readonly, ref, @@ -234,3 +235,24 @@ test('export types', () => { keyof typeof vueReactivityType >() }) + +test('mutate', async () => { + const fn = vi.fn() + + const foo = ref({ foo: 'foo', nested: { count: 1 } }) + watch( + () => foo().nested.count, + () => fn() + ) + + foo.mutate((foo) => { + foo.foo = 'bar' + foo.nested.count++ + }) + + await nextTick() + + expect(foo().foo).toBe('bar') + expect(foo().nested.count).toBe(2) + expect(fn).toBeCalledTimes(1) +}) diff --git a/types.d.ts b/types.d.ts index 3c13537..b8bf4ca 100644 --- a/types.d.ts +++ b/types.d.ts @@ -69,7 +69,12 @@ export declare type FunctionalRef< Writable extends boolean = boolean > = { (): T -} & (Writable extends true ? { set: (value: T) => void } : {}) +} & (Writable extends true + ? { + set: (value: T) => void + mutate: (mutator: (value: T) => void) => void + } + : {}) export declare interface ComputedRef extends WritableComputedRef { set: never