Skip to content

Commit

Permalink
Merge branch 'transformer-not-memorize-outside' of https://github.com…
Browse files Browse the repository at this point in the history
…/upsuper/mobx-utils into upsuper-transformer-not-memorize-outside
  • Loading branch information
mweststrate committed Jan 13, 2020
2 parents 4235a25 + 887d928 commit 9fac6ae
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 12 deletions.
1 change: 1 addition & 0 deletions .watchmanconfig
@@ -0,0 +1 @@
{}
52 changes: 40 additions & 12 deletions src/create-transformer.ts
@@ -1,14 +1,19 @@
import { computed, onBecomeUnobserved, IComputedValue, IComputedValueOptions } from "mobx"
import {
computed,
onBecomeUnobserved,
IComputedValue,
_isComputingDerivation,
IComputedValueOptions
} from "mobx"
import { invariant, addHiddenProp } from "./utils"

export type ITransformer<A, B> = (object: A) => B

export type ITransformerParams<A, B> =
| {
onCleanup?: (resultObject: B | undefined, sourceObject?: A) => void
debugNameGenerator?: (sourceObject?: A) => string
}
| Omit<IComputedValueOptions<B>, "name">
export type ITransformerParams<A, B> = {
onCleanup?: (resultObject: B | undefined, sourceObject?: A) => void
debugNameGenerator?: (sourceObject?: A) => string
keepAlive?: boolean
} & Omit<IComputedValueOptions<B>, "name">

let memoizationId = 0

Expand Down Expand Up @@ -38,7 +43,15 @@ export function createTransformer<A, B>(
// Memoizes: object id -> reactive view that applies transformer to the object
let views: { [id: number]: IComputedValue<B> } = {}
let onCleanup: Function = undefined
let keepAlive: boolean = false
let debugNameGenerator: Function = undefined
if (typeof arg2 === "object") {
onCleanup = arg2.onCleanup
keepAlive = arg2.keepAlive !== undefined ? arg2.keepAlive : false
debugNameGenerator = arg2.debugNameGenerator
} else if (typeof arg2 === "function") {
onCleanup = arg2
}

function createView(sourceIdentifier: number, sourceObject: A) {
let latestValue: B
Expand All @@ -65,18 +78,33 @@ export function createTransformer<A, B>(
name: prettifiedName
}
)
const disposer = onBecomeUnobserved(expr, () => {
delete views[sourceIdentifier]
disposer()
if (onCleanup) onCleanup(latestValue, sourceObject)
})
if (!keepAlive) {
const disposer = onBecomeUnobserved(expr, () => {
delete views[sourceIdentifier]
disposer()
if (onCleanup) onCleanup(latestValue, sourceObject)
})
}
return expr
}

let memoWarned = false
return (object: A) => {
const identifier = getMemoizationId(object)
let reactiveView = views[identifier]
if (reactiveView) return reactiveView.get()
if (!keepAlive && !_isComputingDerivation()) {
if (!memoWarned) {
console.warn(
"invoking a transformer from outside a reactive context won't memorized " +
"and is cleaned up immediately, unless keepAlive is set"
)
memoWarned = true
}
const value = transformer(object)
if (onCleanup) onCleanup(value, object)
return value
}
// Not in cache; create a reactive view
reactiveView = views[identifier] = createView(identifier, object)
return reactiveView.get()
Expand Down
18 changes: 18 additions & 0 deletions test/create-transformer.ts
Expand Up @@ -1369,3 +1369,21 @@ test("supports computed value options", () => {
xs.push(4)
expect(events).toEqual([])
})

test("should not memorize outside reactive context", () => {
let transformer = jest.fn((input: string) => input.toLowerCase())
let onCleanup = jest.fn()
let stubTransformer = createTransformer(transformer, onCleanup)

stubTransformer("HELLO")
expect(transformer).toHaveBeenCalledWith("HELLO")
expect(onCleanup).toHaveBeenCalledWith("hello", "HELLO")
transformer.mockClear()
onCleanup.mockClear()

stubTransformer("HELLO")
expect(transformer).toHaveBeenCalledWith("HELLO")
expect(onCleanup).toHaveBeenCalledWith("hello", "HELLO")
transformer.mockClear()
onCleanup.mockClear()
})

0 comments on commit 9fac6ae

Please sign in to comment.