@dark-engine/core
Version:
The lightweight and powerful UI rendering engine without dependencies and written in TypeScript (Browser, Node.js, Android, iOS, Windows, Linux, macOS)
208 lines (207 loc) • 5.9 kB
JavaScript
import { detectIsFunction, detectIsEmpty, detectAreDepsDifferent, trueFn, logError, formatErrorMsg } from '../utils';
import { createUpdate, useUpdate } from '../use-update';
import { useLayoutEffect } from '../use-layout-effect';
import { ATOM_HOST_MASK } from '../constants';
import { $$scope, getRootId } from '../scope';
import { createTools } from '../use-state';
import { EventEmitter } from '../emitter';
import { useMemo } from '../use-memo';
import { batch } from '../batch';
class Atom {
value;
connections1;
connections2;
subjects;
emitter;
constructor(value) {
this.value = value;
}
val(fn, key) {
try {
this.__connect(fn, key);
} catch (err) {
if (process.env.NODE_ENV !== 'production') {
logError(formatErrorMsg(`Illegal invocation atom.val() outside render process!`));
}
}
return this.value;
}
get() {
return this.value;
}
on(fn) {
!this.emitter && (this.emitter = new EventEmitter());
return this.emitter.on('data', fn);
}
kill() {
if (this.connections1) {
for (const [hook, [_, __, ___, key]] of this.connections1) {
this.off(hook, key);
}
}
if (this.connections2) {
for (const [key, [_, hook]] of this.connections2) {
this.off(hook, key);
}
}
this.connections1 = null;
this.connections2 = null;
this.emitter = null;
this.subjects = null;
}
toString() {
return String(this.value);
}
toJSON() {
return this.value;
}
valueOf() {
return this.value;
}
__connect(fn, key) {
const rootId = getRootId();
const cursor = $$scope().getCursor();
const { hook } = cursor;
const disconnect = () => this.off(hook, key);
hook.setAtom(this, disconnect);
cursor.markHost(ATOM_HOST_MASK);
if (detectIsEmpty(key)) {
!this.connections1 && (this.connections1 = new Map());
this.connections1.set(hook, [rootId, hook, fn, key]);
} else {
!this.connections2 && (this.connections2 = new Map());
this.connections2.set(key, [rootId, hook, fn, key]);
}
return disconnect;
}
__addSubject(atom$) {
!this.subjects && (this.subjects = new Set());
this.subjects.add(atom$);
}
__removeSubject(atom$) {
return this.subjects && this.subjects.delete(atom$);
}
__getSize() {
const size1 = this.connections1 ? this.connections1.size : 0;
const size2 = this.connections2 ? this.connections2.size : 0;
const size3 = this.subjects ? this.subjects.size : 0;
const size4 = this.emitter ? this.emitter.__getSize() : 0;
return size1 + size2 + size3 + size4;
}
setValue(value) {
const prev = this.value;
const next = detectIsFunction(value) ? value(this.value) : value;
const data = { prev, next };
const make = (tuple, prev, next) => {
const [rootId, hook, shouldUpdate, key] = tuple;
const fn = shouldUpdate || trueFn;
if (fn(prev, next, key)) {
const update = createUpdate(rootId, hook);
if (this.__getSize() === 1) {
const tools = createTools({
next,
get: () => prev,
set: () => (this.value = next),
reset: () => (this.value = prev),
});
update(tools);
} else {
update();
}
}
};
this.value = next;
if (this.connections1) {
for (const [_, tuple] of this.connections1) {
make(tuple, prev, next);
}
}
if (this.connections2) {
if (this.connections2.has(next)) {
make(this.connections2.get(next), prev, next);
this.connections2.has(prev) && make(this.connections2.get(prev), prev, next);
}
}
this.emitter && this.emitter.emit('data', data);
this.subjects && this.subjects.forEach(x => x.__notify());
}
off(hook, key) {
hook.removeAtom(this);
this.connections1 && this.connections1.delete(hook);
this.connections2 && this.connections2.delete(key);
}
}
class WritableAtom extends Atom {
set(value) {
super.setValue(value);
}
}
class ReadableAtom extends Atom {
deps$ = [];
fn = null;
values = [];
constructor(deps$, fn) {
const values = ReadableAtom.values(deps$);
super(ReadableAtom.compute(fn, values));
this.deps$ = deps$;
this.fn = fn;
this.values = values;
deps$.forEach(x => x.__addSubject(this));
}
__notify() {
const values = ReadableAtom.values(this.deps$);
if (detectAreDepsDifferent(this.values, values)) {
super.setValue(ReadableAtom.compute(this.fn, values));
}
this.values = values;
}
kill() {
super.kill();
this.deps$.forEach(x => x.__removeSubject(this));
this.deps$ = [];
this.fn = null;
}
static compute(fn, values) {
return fn(...values);
}
static values(deps$) {
return deps$.map(x => x.get());
}
}
const detectIsAtom = x => x instanceof Atom;
const detectIsWritableAtom = x => x instanceof WritableAtom;
const detectIsReadableAtom = x => x instanceof ReadableAtom;
const atom = value => new WritableAtom(value);
const computed = (deps$, fn) => new ReadableAtom(deps$, fn);
function useAtom(value) {
const atom$ = useMemo(() => atom(value), []);
useLayoutEffect(() => () => atom$.kill(), []);
return atom$;
}
function useComputed(deps$, fn) {
const atom$ = useMemo(() => computed(deps$, fn), []);
useLayoutEffect(() => () => atom$.kill(), []);
return atom$;
}
function useStore(atoms$) {
const forceUpdate = useUpdate();
const update = () => batch(forceUpdate);
useLayoutEffect(() => {
const offs = atoms$.map(x => x.on(update));
return () => offs.forEach(x => x());
}, [...atoms$]);
return atoms$.map(x => x.get());
}
export {
WritableAtom,
ReadableAtom,
detectIsAtom,
detectIsWritableAtom,
detectIsReadableAtom,
atom,
computed,
useAtom,
useComputed,
useStore,
};
//# sourceMappingURL=atom.js.map