UNPKG

@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
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