Skip to content

Instantly share code, notes, and snippets.

@djalmajr
Last active April 14, 2023 15:37
Show Gist options
  • Save djalmajr/6f7c24f42dcfb8d80991d50ea2ff8898 to your computer and use it in GitHub Desktop.
Save djalmajr/6f7c24f42dcfb8d80991d50ea2ff8898 to your computer and use it in GitHub Desktop.
Reactive Store
const typeOf = (v) => ({}.toString.call(v).slice(8, -1).toLowerCase());
const isObj = (v) => ["array", "object"].includes(typeOf(v));
const isFn = (v) => "function" === typeOf(v);
function handler(callback) {
return {
get(obj, prop) {
if (prop === "_isProxy") return true;
let d = Object.getOwnPropertyDescriptor(obj, prop);
let ok = isObj(obj[prop]) && !obj[prop]._isProxy && d?.writable;
ok && (obj[prop] = new Proxy(obj[prop], handler(callback)));
return obj[prop];
},
set(obj, prop, value) {
if (obj[prop] !== value) (obj[prop] = value), callback(obj);
return true;
},
deleteProperty(obj, prop) {
delete obj[prop];
callback(obj);
return true;
},
};
}
export function createStore(init = {}) {
const fns = [];
const subscribe = (fn) => {
if (!fns.includes(fn)) fns.push(fn);
return () => {
const idx = fns.findIndex((l) => l === fn);
idx !== -1 && fns.splice(idx, 1);
};
};
let nextTickId = 0;
const store = new Proxy(
Object.assign(init, { subscribe }),
handler(({ subscribe, ...data }) => {
if (nextTickId) cancelAnimationFrame(nextTickId);
nextTickId = requestAnimationFrame(() => fns.forEach((fn) => fn(data)));
})
);
for (const prop in init) {
const d = Object.getOwnPropertyDescriptor(init, prop);
if (d?.get && d?.configurable) {
Object.defineProperty(init, prop, {
get: d.get.bind(store),
configurable: false,
});
} else if (isFn(init[prop])) {
Object.defineProperty(init, prop, {
value: init[prop].bind(store),
configurable: false,
writable: false,
});
}
}
return store;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment