classy-solid
Version:
Solid.js reactivity patterns for classes, and class components.
83 lines (77 loc) • 2.86 kB
JavaScript
// TODO switch to non-dep-tracking non-queue-modifying deferred signals, because those do not break with regular effects.
import { createSignal as _createSignal, createEffect, onCleanup, getOwner, runWithOwner } from 'solid-js';
const effectQueue = new Set();
let runningEffects = false;
// map of effects to dependencies
const effectDeps = new Map();
let currentEffect = () => {};
// Override createSignal in order to implement custom tracking of effect
// dependencies, so that when signals change, we are aware which dependenct
// effects need to be moved to the end of the effect queue while running
// deferred effects in a microtask.
export let createSignal = (value, options) => {
let [_get, _set] = _createSignal(value, options);
const get = () => {
if (!runningEffects) return _get();
let deps = effectDeps.get(currentEffect);
if (!deps) effectDeps.set(currentEffect, deps = new Set());
deps.add(_set);
return _get();
};
const set = v => {
if (!runningEffects) return _set(v);
// This is inefficient, for proof of concept, unable to use Solid
// internals on the outside.
for (const [fn, deps] of effectDeps) {
for (const dep of deps) {
if (dep === _set) {
// move to the end
effectQueue.delete(fn);
effectQueue.add(fn);
}
}
}
return _set(v);
};
return [get, set];
};
let effectTaskIsScheduled = false;
// TODO Option so the first run is deferred instead of immediate? This already
// happens outside of a root.
export const createDeferredEffect = (fn, value, options) => {
let initial = true;
createEffect(prev => {
if (initial) {
initial = false;
currentEffect = fn;
effectDeps.get(fn)?.clear(); // clear to track deps, or else it won't track new deps based on code branching
fn(prev);
return;
}
effectQueue.add(fn); // add, or move to the end, of the queue. TODO This is probably redundant now, but I haven't tested yet.
// If we're currently running the queue, return because fn will run
// again at the end of the queue iteration due to our overriden
// createSignal moving it to the end.
if (runningEffects) return;
if (effectTaskIsScheduled) return;
effectTaskIsScheduled = true;
const owner = getOwner();
queueMicrotask(() => {
if (owner) runWithOwner(owner, runEffects);else runEffects();
});
}, value, options);
getOwner() && onCleanup(() => {
effectDeps.delete(fn);
effectQueue.delete(fn);
});
};
function runEffects() {
runningEffects = true;
for (const fn of effectQueue) {
effectQueue.delete(fn); // TODO This is probably redundant now, but I haven't tested yet.
createDeferredEffect(fn);
}
runningEffects = false;
effectTaskIsScheduled = false;
}
//# sourceMappingURL=createDeferredEffect.js.map