diff --git a/src/computedFn.ts b/src/computedFn.ts index 3b269aa..8afa258 100644 --- a/src/computedFn.ts +++ b/src/computedFn.ts @@ -8,6 +8,10 @@ import { isAction, } from "mobx" +export type IComputedFnOptions any> = { + onCleanup?: (result: ReturnType | undefined, ...args: Parameters) => void +} & IComputedValueOptions> + /** * computedFn takes a function with an arbitrary amount of arguments, * and memoizes the output of the function based on the arguments passed in. @@ -44,7 +48,7 @@ import { */ export function computedFn any>( fn: T, - keepAliveOrOptions: IComputedValueOptions> | boolean = false + keepAliveOrOptions: IComputedFnOptions | boolean = false ): T { if (isAction(fn)) throw new Error("computedFn shouldn't be used on actions") @@ -56,7 +60,7 @@ export function computedFn any>( : keepAliveOrOptions const d = new DeepMap>() - return function (this: any, ...args: any[]): any { + return function (this: any, ...args: Parameters): ReturnType { const entry = d.entry(args) // cache hit, return if (entry.exists()) return entry.get().get() @@ -71,9 +75,10 @@ export function computedFn any>( return fn.apply(this, args) } // create new entry + let latestValue: ReturnType | undefined const c = computed( () => { - return fn.apply(this, args) + return (latestValue = fn.apply(this, args)) }, { ...opts, @@ -85,6 +90,8 @@ export function computedFn any>( if (!opts.keepAlive) onBecomeUnobserved(c, () => { d.entry(args).delete() + if (opts.onCleanup) opts.onCleanup(latestValue, ...args) + latestValue = undefined }) // return current val return c.get() diff --git a/test/computedFn.ts b/test/computedFn.ts index fcf31bf..e2ed700 100644 --- a/test/computedFn.ts +++ b/test/computedFn.ts @@ -175,6 +175,22 @@ test("supports options", () => { expect(events).toEqual([]) }) +test("supports onCleanup", () => { + const sep = observable.box(".") + const unloaded = [] + const joinedStr = computedFn((sep) => [1, 2, 3].join(sep), { + onCleanup: (result, sep) => unloaded.push([result, sep]), + }) + autorun(() => joinedStr(sep.get())) + sep.set(",") + expect(unloaded.length).toBe(1) + sep.set(" ") + expect(unloaded).toEqual([ + ["1.2.3", "."], + ["1,2,3", ","], + ]) +}) + test("should not allow actions", () => { expect(() => computedFn(action(() => {}))).toThrow("action") })