Skip to content

Commit f56e0fb

Browse files
authored
Merge pull request #331 from peterm-canva/memo-symbol
Use a Map for createTransformer memoization
2 parents 855baae + 1f2a17e commit f56e0fb

File tree

2 files changed

+32
-28
lines changed

2 files changed

+32
-28
lines changed

src/create-transformer.ts

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,21 @@ import {
55
_isComputingDerivation,
66
IComputedValueOptions,
77
} from "mobx"
8-
import { invariant, addHiddenProp } from "./utils"
8+
import { invariant } from "./utils"
99

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

12+
export type ITransformerCleanup<A, B> = (resultObject: B | undefined, sourceObject?: A) => void
13+
1214
export type ITransformerParams<A, B> = {
13-
onCleanup?: (resultObject: B | undefined, sourceObject?: A) => void
15+
onCleanup?: ITransformerCleanup<A, B>
1416
debugNameGenerator?: (sourceObject?: A) => string
1517
keepAlive?: boolean
1618
} & Omit<IComputedValueOptions<B>, "name">
1719

18-
let memoizationId = 0
19-
2020
export function createTransformer<A, B>(
2121
transformer: ITransformer<A, B>,
22-
onCleanup?: (resultObject: B | undefined, sourceObject?: A) => void
22+
onCleanup?: ITransformerCleanup<A, B>
2323
): ITransformer<A, B>
2424
export function createTransformer<A, B>(
2525
transformer: ITransformer<A, B>,
@@ -31,20 +31,22 @@ export function createTransformer<A, B>(
3131
*
3232
* See the [transformer](#createtransformer-in-detail) section for more details.
3333
*
34-
* @param transformer
35-
* @param onCleanup
34+
* @param transformer A function which transforms instances of A into instances of B
35+
* @param arg2 An optional cleanup function which is called when the transformation is no longer
36+
* observed from a reactive context, or config options
37+
* @returns The memoized transformer function
3638
*/
3739
export function createTransformer<A, B>(
3840
transformer: ITransformer<A, B>,
39-
arg2?: any
41+
arg2?: ITransformerParams<A, B> | ITransformerCleanup<A, B>
4042
): ITransformer<A, B> {
4143
invariant(
4244
typeof transformer === "function" && transformer.length < 2,
4345
"createTransformer expects a function that accepts one argument"
4446
)
4547

46-
// Memoizes: object id -> reactive view that applies transformer to the object
47-
let views: { [id: number]: IComputedValue<B> } = {}
48+
// Memoizes: object -> reactive view that applies transformer to the object
49+
const views = new Map<A, IComputedValue<B>>()
4850
let onCleanup: Function | undefined = undefined
4951
let keepAlive: boolean = false
5052
let debugNameGenerator: Function | undefined = undefined
@@ -56,7 +58,7 @@ export function createTransformer<A, B>(
5658
onCleanup = arg2
5759
}
5860

59-
function createView(sourceIdentifier: number, sourceObject: A) {
61+
function createView(sourceObject: A) {
6062
let latestValue: B
6163
let computedValueOptions = {}
6264
if (typeof arg2 === "object") {
@@ -69,9 +71,12 @@ export function createTransformer<A, B>(
6971
onCleanup = undefined
7072
debugNameGenerator = undefined
7173
}
74+
const sourceType = typeof sourceObject
7275
const prettifiedName = debugNameGenerator
7376
? debugNameGenerator(sourceObject)
74-
: `Transformer-${(<any>transformer).name}-${sourceIdentifier}`
77+
: `Transformer-${(<any>transformer).name}-${
78+
sourceType === "string" || sourceType === "number" ? sourceObject : "object"
79+
}`
7580
const expr = computed(
7681
() => {
7782
return (latestValue = transformer(sourceObject))
@@ -83,7 +88,7 @@ export function createTransformer<A, B>(
8388
)
8489
if (!keepAlive) {
8590
const disposer = onBecomeUnobserved(expr, () => {
86-
delete views[sourceIdentifier]
91+
views.delete(sourceObject)
8792
disposer()
8893
if (onCleanup) onCleanup(latestValue, sourceObject)
8994
})
@@ -93,8 +98,8 @@ export function createTransformer<A, B>(
9398

9499
let memoWarned = false
95100
return (object: A) => {
96-
const identifier = getMemoizationId(object)
97-
let reactiveView = views[identifier]
101+
checkTransformableObject(object)
102+
let reactiveView = views.get(object)
98103
if (reactiveView) return reactiveView.get()
99104
if (!keepAlive && !_isComputingDerivation()) {
100105
if (!memoWarned) {
@@ -109,25 +114,24 @@ export function createTransformer<A, B>(
109114
return value
110115
}
111116
// Not in cache; create a reactive view
112-
reactiveView = views[identifier] = createView(identifier, object)
117+
reactiveView = createView(object)
118+
views.set(object, reactiveView)
113119
return reactiveView.get()
114120
}
115121
}
116122

117-
function getMemoizationId(object: any) {
123+
function checkTransformableObject(object: any) {
118124
const objectType = typeof object
119-
if (objectType === "string") return `string:${object}`
120-
if (objectType === "number") return `number:${object}`
121-
if (object === null || (objectType !== "object" && objectType !== "function"))
125+
if (
126+
object === null ||
127+
(objectType !== "object" &&
128+
objectType !== "function" &&
129+
objectType !== "string" &&
130+
objectType !== "number")
131+
)
122132
throw new Error(
123133
`[mobx-utils] transform expected an object, function, string or number, got: ${String(
124134
object
125135
)}`
126136
)
127-
let tid = object.$transformId
128-
if (tid === undefined) {
129-
tid = `memoizationId:${++memoizationId}`
130-
addHiddenProp(object, "$transformId", tid)
131-
}
132-
return tid
133137
}

test/create-transformer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ test("transform1", () => {
7272
"name": "[email protected][..].title",
7373
"observers": Array [
7474
Object {
75-
"name": "Transformer--memoizationId:3",
75+
"name": "Transformer--object",
7676
"observers": Array [
7777
Object {
78-
"name": "Transformer--memoizationId:1",
78+
"name": "Transformer--object",
7979
"observers": Array [
8080
Object {
8181
"name": "Autorun@2",

0 commit comments

Comments
 (0)