Skip to content

Commit

Permalink
refactor: lazy init, support for delayed observers
Browse files Browse the repository at this point in the history
  • Loading branch information
yesil committed Dec 16, 2024
1 parent f02bdad commit aefcbd2
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 177 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Currently, the non minified bundle size is `4938 bytes` and is around `~1.2kb` g
- `reaction`: react to specific changes
- `computed`: cache computed values

Also, an opinionated approach for tracking nested dependencies with `track/untrack` functions is added. <br>
Also, an opinionated approach for tracking nested dependencies with `track` functions is added. <br>
If an observable wants to get notified by another one, these functions can be used.

## How to Use
Expand Down
152 changes: 70 additions & 82 deletions dist/picosm.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ function instrumentAction(target, methodName) {
descriptor.value = async function(...args) {
const response = await originalMethod.call(this, ...args);
this.__resetComputedProperties();
this.__notifyListeners();
this.__notifyObservers();
return response;
};
} else {
descriptor.value = function(...args) {
const response = originalMethod.call(this, ...args);
this.__resetComputedProperties();
this.__notifyListeners();
this.__notifyObservers();
return response;
};
}
Expand All @@ -28,43 +28,49 @@ function instrumentComputed(target, getterName) {
if (descriptor && typeof descriptor.get === "function") {
const originalGetter = descriptor.get;
descriptor.get = function() {
if (this.__computedValues.has(getterName)) {
return this.__computedValues.get(getterName);
if (!this.__computedProperties) {
Object.defineProperties(
this,
{
__computedProperties: { value: /* @__PURE__ */ new Map() }
},
{
__computedProperties: { enumerable: false, writable: false }
}
);
}
if (this.__computedProperties.has(getterName)) {
return this.__computedProperties.get(getterName);
}
const cachedValue = originalGetter.call(this);
this.__computedValues.set(getterName, cachedValue);
this.__computedProperties.set(getterName, cachedValue);
return cachedValue;
};
Object.defineProperty(prototype, getterName, descriptor);
}
}
function makeObservable(constructor2, actions = [], computeds = []) {
class SuperClass extends constructor2 {
constructor(...args) {
super(...args);
Object.defineProperties(
this,
{
__observers: { value: /* @__PURE__ */ new Set() },
__computedValues: { value: /* @__PURE__ */ new Map() },
__dependencies: { value: /* @__PURE__ */ new WeakMap() }
},
{
__observers: { enumerable: false, writable: false },
__computedValues: { enumerable: false, writable: false },
__dependencies: { enumerable: false, writable: false }
}
);
}
__notifyListeners() {
this.__observers.forEach((listener) => {
__notifyObservers() {
this.__observers?.forEach((listener) => {
listener();
});
}
__resetComputedProperties() {
this.__computedValues.clear();
this.__computedProperties?.clear();
}
__observe(callback) {
if (!this.__observers) {
Object.defineProperties(
this,
{
__observers: { value: /* @__PURE__ */ new Set() }
},
{
__observers: { enumerable: false, writable: false }
}
);
}
this.__observers.add(callback);
return () => {
this.__observers.delete(callback);
Expand All @@ -79,63 +85,55 @@ function makeObservable(constructor2, actions = [], computeds = []) {
});
return SuperClass;
}
function observe(target, callback) {
return target.__observe(callback);
}
function observeSlow(timeout) {
return (target, callback) => {
let timer;
const listener = () => {
clearTimeout(timer);
timer = setTimeout(callback, timeout);
};
return target.__observe(listener);
function observeSlow(target, callback, timeout) {
let timer;
const listener = () => {
clearTimeout(timer);
timer = setTimeout(callback, timeout);
};
return target.__observe(listener);
}
function observe(target, callback, timeout) {
if (timeout) {
return observeSlow(target, callback, timeout);
} else {
return target.__observe(callback);
}
}

// src/reaction.js
function reaction(target, callback, execute) {
function reaction(target, callback, execute, timeout) {
let lastProps = [];
return target.__observe(async () => {
const props = callback(target);
if (lastProps === props)
return;
let shouldExecute = false;
for (let i = 0; i < props.length; i++) {
if (lastProps[i] !== props[i]) {
shouldExecute = true;
break;
return observe(
target,
async () => {
const props = callback(target);
if (lastProps === props)
return;
let shouldExecute = false;
for (let i = 0; i < props.length; i++) {
if (lastProps[i] !== props[i]) {
shouldExecute = true;
break;
}
}
}
if (shouldExecute) {
lastProps = props;
execute(...props);
}
});
if (shouldExecute) {
lastProps = props;
execute(...props);
}
},
timeout
);
}

// src/track.js
function track(target, source) {
if (!target.__observers || !source?.__observers)
return;
const disposer = source.__observe(() => {
const disposer = source.__observe?.(() => {
target.__resetComputedProperties();
target.__notifyListeners();
target.__notifyObservers();
});
target.__dependencies.set(source, disposer);
target.__resetComputedProperties();
target.__notifyListeners();
}
function untrack(target, source) {
if (!target.__observers || !source?.__observers)
return;
const disposer = target.__dependencies.get(source);
if (disposer) {
target.__dependencies.delete(source);
disposer();
target.__resetComputedProperties();
target.__notifyListeners();
}
target.__notifyObservers();
}

// src/LitObserver.js
Expand All @@ -150,35 +148,27 @@ function litObserver(constructor, properties) {
properties.forEach((property) => {
let observableProperty;
let delay;
let observeFn2 = observe;
if (Array.isArray(property)) {
observableProperty = this[property[0]];
delay = property[1];
observeFn2 = observeSlow(delay);
} else {
observableProperty = this[property];
}
if (!observableProperty?.__observers)
return;
if (this.#observables.has(observableProperty)) {
return;
}
if (!observableProperty)
return;
this.#observables.add(observableProperty);
this.#disposers.add(
observeFn2(observableProperty, this.requestUpdate.bind(this))
observe(observableProperty, this.requestUpdate.bind(this), delay)
);
});
}
updated(changedProperties) {
super.updated(changedProperties);
this.trackProperties();
}
connectedCallback() {
super.connectedCallback();
this.#observables.forEach((o) => {
this.#disposers.add(observeFn(o, this.requestUpdate.bind(this)));
});
}
disconnectedCallback() {
super.disconnectedCallback();
this.#disposers.forEach((disposer) => {
Expand All @@ -193,8 +183,6 @@ export {
litObserver,
makeObservable,
observe,
observeSlow,
reaction,
track,
untrack
track
};
15 changes: 3 additions & 12 deletions src/LitObserver.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { observe, observeSlow } from './makeObservable.js';
import { observe } from './makeObservable.js';

export function litObserver(constructor, properties) {
class LitObserver extends constructor {
Expand All @@ -12,21 +12,19 @@ export function litObserver(constructor, properties) {
properties.forEach((property) => {
let observableProperty;
let delay;
let observeFn = observe;
if (Array.isArray(property)) {
observableProperty = this[property[0]];
delay = property[1];
observeFn = observeSlow(delay);
} else {
observableProperty = this[property];
}
if (!observableProperty?.__observers) return;
if (this.#observables.has(observableProperty)) {
return;
}
if (!observableProperty) return;
this.#observables.add(observableProperty);
this.#disposers.add(
observeFn(observableProperty, this.requestUpdate.bind(this)),
observe(observableProperty, this.requestUpdate.bind(this), delay),
);
});
}
Expand All @@ -36,13 +34,6 @@ export function litObserver(constructor, properties) {
this.trackProperties();
}

connectedCallback() {
super.connectedCallback();
this.#observables.forEach((o) => {
this.#disposers.add(observeFn(o, this.requestUpdate.bind(this)));
});
}

disconnectedCallback() {
super.disconnectedCallback();
this.#disposers.forEach((disposer) => {
Expand Down
Loading

0 comments on commit aefcbd2

Please sign in to comment.