Skip to content

Commit

Permalink
perf: optimize reactive state handlers (#69)
Browse files Browse the repository at this point in the history
* perf: optimize reactive state handlers

* perf: avoid creating readonly handler unnecessarily

* fix: add missing semicolon

* fix: add another missing semicolon

* fix: add yet another missing semicolon

* fix: duplicate the methods
  • Loading branch information
nolanlawson authored Nov 1, 2021
1 parent 3595c5c commit cb575cd
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 41 deletions.
70 changes: 29 additions & 41 deletions src/reactive-membrane.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
ObjectDefineProperty,
unwrap,
isArray,
isUndefined,
Expand All @@ -15,11 +14,6 @@ if (process.env.NODE_ENV !== 'production') {
initDevFormatter();
}

interface ReactiveState {
readOnly: any;
reactive: any;
}

export type ReactiveMembraneAccessCallback = (obj: any, key: ProxyPropertyKey) => void;
export type ReactiveMembraneMutationCallback = (obj: any, key: ProxyPropertyKey) => void;
export type ReactiveMembraneDistortionCallback = (value: any) => any;
Expand Down Expand Up @@ -72,7 +66,8 @@ export class ReactiveMembrane {
valueObserved: ReactiveMembraneAccessCallback = defaultValueObserved;
valueIsObservable: ReactiveMembraneObservableCallback = defaultValueIsObservable;
tagPropertyKey: ProxyPropertyKey | undefined;
private objectGraph: WeakMap<any, ReactiveState> = new WeakMap();
private readOnlyObjectGraph: WeakMap<any, any> = new WeakMap();
private reactiveObjectGraph: WeakMap<any, any> = new WeakMap();

constructor(options?: ObservableMembraneInit) {
if (!isUndefined(options)) {
Expand All @@ -89,10 +84,12 @@ export class ReactiveMembrane {
const unwrappedValue = unwrap(value);
const distorted = this.valueDistortion(unwrappedValue);
if (this.valueIsObservable(distorted)) {
const o = this.getReactiveState(unwrappedValue, distorted);
// when trying to extract the writable version of a readonly
// we return the readonly.
return o.readOnly === value ? value : o.reactive;
if (this.readOnlyObjectGraph.get(distorted) === value) {
// when trying to extract the writable version of a readonly
// we return the readonly.
return value;
}
return this.getReactiveHandler(unwrappedValue, distorted);
}
return distorted;
}
Expand All @@ -101,7 +98,7 @@ export class ReactiveMembrane {
value = unwrap(value);
const distorted = this.valueDistortion(value);
if (this.valueIsObservable(distorted)) {
return this.getReactiveState(value, distorted).readOnly;
return this.getReadOnlyHandler(value, distorted);
}
return distorted;
}
Expand All @@ -110,36 +107,27 @@ export class ReactiveMembrane {
return unwrap(p);
}

private getReactiveState(value: any, distortedValue: any): ReactiveState {
const {
objectGraph,
} = this;
let reactiveState = objectGraph.get(distortedValue);
if (reactiveState) {
return reactiveState;
private getReactiveHandler(value: any, distortedValue: any): any {
let proxy = this.reactiveObjectGraph.get(distortedValue);
if (isUndefined(proxy)) {
// caching the proxy after the first time it is accessed
const handler = new ReactiveProxyHandler(this, distortedValue);
proxy = new Proxy(createShadowTarget(distortedValue), handler);
registerProxy(proxy, value);
this.reactiveObjectGraph.set(distortedValue, proxy);
}
const membrane = this;
reactiveState = {
get reactive() {
const reactiveHandler = new ReactiveProxyHandler(membrane, distortedValue);
// caching the reactive proxy after the first time it is accessed
const proxy = new Proxy(createShadowTarget(distortedValue), reactiveHandler);
registerProxy(proxy, value);
ObjectDefineProperty(this, 'reactive', { value: proxy });
return proxy;
},
get readOnly() {
const readOnlyHandler = new ReadOnlyHandler(membrane, distortedValue);
// caching the readOnly proxy after the first time it is accessed
const proxy = new Proxy(createShadowTarget(distortedValue), readOnlyHandler);
registerProxy(proxy, value);
ObjectDefineProperty(this, 'readOnly', { value: proxy });
return proxy;
}
} as ReactiveState;

objectGraph.set(distortedValue, reactiveState);
return reactiveState;
return proxy;
}

private getReadOnlyHandler(value: any, distortedValue: any): any {
let proxy = this.readOnlyObjectGraph.get(distortedValue);
if (isUndefined(proxy)) {
// caching the proxy after the first time it is accessed
const handler = new ReadOnlyHandler(this, distortedValue);
proxy = new Proxy(createShadowTarget(distortedValue), handler);
registerProxy(proxy, value);
this.readOnlyObjectGraph.set(distortedValue, proxy);
}
return proxy;
}
}
5 changes: 5 additions & 0 deletions test/reactive-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1028,4 +1028,9 @@ describe('ReactiveHandler', () => {
expect((dry as any).$$MagicKey$$).toBe('bar');
});
});
it('should return undefined when input is undefined', () => {
const target = new ReactiveMembrane();
const state = target.getProxy(undefined);
expect(state).toBe(undefined);
});
});
5 changes: 5 additions & 0 deletions test/read-only-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,4 +435,9 @@ describe('ReadOnlyHandler', () => {
`"Invalid mutation: Cannot mutate array at index 0. Array is read-only."`
);
});
it('should return undefined when input is undefined', () => {
const target = new ReactiveMembrane();
const state = target.getReadOnlyProxy(undefined);
expect(state).toBe(undefined);
});
});

0 comments on commit cb575cd

Please sign in to comment.