UNPKG

@msom/reaction

Version:

@msom/reaction

535 lines (524 loc) 16.3 kB
import { ComputedDecoratorUsedError, ObserverDecoratorUsedError, assert, defineAccesser, defineProperty, equal, getGlobalData, initComponentDefinition, isComponent, isObject, setGlobalData } from "@msom/common"; //#region src/Reaction/index.ts setGlobalData("@msom/reaction", {}); var Reaction = class { constructor(option) { this.tracked = /* @__PURE__ */new Set(); this.option = option; this.updateNextTick(); this.track(); } _cancel() { this.cancel && this.cancel(); this.cancel = void 0; } /** * 根据传入的delay选项,初始化微队列函数 * @returns */ updateNextTick() { const { scheduler } = this.option; if (!scheduler) return; if (scheduler === "nextTick") {if (typeof process !== "undefined" && process.nextTick) this.nextTick = function (cb) { this._cancel(); let canceled = false; process.nextTick(() => { if (!canceled) cb(); this.cancel = void 0; }); this.cancel = () => { canceled = true; }; };else this.nextTick = function (cb) { this._cancel(); let canceled = false; queueMicrotask(() => { if (!canceled) cb(); this.cancel = void 0; }); this.cancel = () => { canceled = true; }; };} else if (scheduler === "nextFrame") this.nextTick = function (cb) { this._cancel(); if (Reflect.has(globalThis, "requestAnimationFrame")) { const rafId = requestAnimationFrame(() => { cb(); this.cancel = void 0; }); this.cancel = () => { cancelAnimationFrame(rafId); this.cancel = void 0; }; } else { const id = setTimeout(() => { cb(); this.cancel = void 0; }, 1e3 / 60); this.cancel = () => { clearTimeout(id); this.cancel = void 0; }; } };else this.nextTick = scheduler; } nextTick(cb) { this._cancel(); cb(); } track() { const { tracker } = this.option; this.destroy(); const reactionData = getGlobalData("@msom/reaction"); const { tracking, reaction } = reactionData; try { Object.assign(reactionData, { tracking: this.addObserver.bind(this), reaction: this }); tracker(); } catch (e) { this.destroy(); console.error(e); } finally { Object.assign(reactionData, { tracking, reaction }); } } notify() { const { reaction } = getGlobalData("@msom/reaction"); if (reaction && reaction === this) console.error("The value of the dependent observer is being changed in the current tracking");else this.nextTick(() => { this.runcall(); }); } exec() { this.runcall(); return this; } runcall() { const { callback } = this.option; callback ? callback() : this.track(); } disposer() { return this.destroy.bind(this); } destroy() { this.tracked.forEach(this.removeObserver.bind(this)); } addObserver(observer$1) { this.tracked.add(observer$1); observer$1.addReaction(this); } removeObserver(observer$1) { this.tracked.delete(observer$1); observer$1.removeReaction(this); } }; function createReaction(tracker, callback, option) { if (typeof callback === "function") return new Reaction({ tracker, callback, scheduler: option?.scheduler });else if (callback) { if (option) throw "error params."; return new Reaction({ tracker, scheduler: callback.scheduler }); } else return new Reaction({ tracker, scheduler: option?.scheduler }); } function withoutTrack(callback) { const reactionData = getGlobalData("@msom/reaction"); const { tracking, reaction } = reactionData; reactionData.tracking = void 0; reactionData.reaction = void 0; try { return callback(); } catch (e) { throw e; } finally { Object.assign(reactionData, { tracking, reaction }); } } //#endregion //#region src/Operator/index.ts const iterator = Symbol.iterator; const OPERATORTYPES = { TRACKER: { GET: "GET", HAS: "HAS", [iterator]: iterator }, TRRIGER: { SET: "SET", ADD: "ADD", DELETE: "DELETE" } }; const OPERATORMAPS = { [OPERATORTYPES.TRRIGER.SET]: [OPERATORTYPES.TRACKER.GET], [OPERATORTYPES.TRRIGER.ADD]: [ OPERATORTYPES.TRACKER.GET, OPERATORTYPES.TRACKER.HAS, OPERATORTYPES.TRACKER[iterator]], [OPERATORTYPES.TRRIGER.DELETE]: [ OPERATORTYPES.TRACKER.GET, OPERATORTYPES.TRACKER.HAS, OPERATORTYPES.TRACKER[iterator]] }; Object.freeze(OPERATORTYPES); Object.freeze(OPERATORMAPS); //#endregion //#region src/Observer/index.ts var Observer = class { constructor(option = {}) { this.destroyed = false; this.equal = option.equal || equal; this.handlers = /* @__PURE__ */new Set(); if (Reflect.has(option, "initValue")) { const value = option.initValue; assert(value); this.value = value; } this.$option = option; } track() { const running = getGlobalData("@msom/reaction"); if (running?.tracking) running.tracking(this); } reactiveValue(value) { return this.$option.deep && isObject(value) ? reactive(value) : value; } get() { if (this.destroyed) return this.value; this.track(); return this.value; } set(newValue) { const { value: oldValue } = this; this.value = this.reactiveValue(newValue); if (!this.destroyed && !this.equal(oldValue, newValue)) this.notify(); } notify() { const handles = [...this.handlers]; for (const reaction of handles) reaction.notify(); } addReaction(reaction) { this.handlers.add(reaction); } removeReaction(reaction) { this.handlers.delete(reaction); } destroy() { this.handlers.forEach((reaction) => { reaction.removeObserver(this); }); this.destroyed = true; } }; function getObserverForce(target, propKey, trackType) { let _target = targetMap.get(target); if (!_target) targetMap.set(target, _target = { propMap: /* @__PURE__ */new Map() }); if (!_target.propMap) _target.propMap = /* @__PURE__ */new Map(); const propMap = _target.propMap; let typesMap = propMap.get(propKey); if (!typesMap) propMap.set(propKey, typesMap = /* @__PURE__ */new Map()); let observer$1 = typesMap.get(trackType); if (!observer$1) typesMap.set(trackType, observer$1 = new Observer({ initValue: target[propKey] })); return observer$1; } function getObserver$1(target, propKey, trackType) { return targetMap.get(target)?.propMap?.get(propKey)?.get(trackType); } const targetMap = /* @__PURE__ */new WeakMap(); function track(target, propKey, trackType) { getObserverForce(target, propKey, trackType).track(); } function trriger(target, propKey, trrigerType, destroy) { const trackTypes = OPERATORMAPS[trrigerType]; for (const trackType of trackTypes) { const observer$1 = getObserver$1(target, propKey, trackType); if (!observer$1) continue; observer$1.notify(); destroy && observer$1.destroy(); } } const reactiveOptions = { get(target, propKey, receiver) { const value = Reflect.get(target, propKey, receiver); track(target, propKey, OPERATORTYPES.TRACKER.GET); if (isObject(value)) return reactive(value); if (typeof value === "function") return value.bind(receiver); return value; }, has(target, propKey) { const result = Reflect.has(target, propKey); track(target, propKey, OPERATORTYPES.TRACKER.HAS); return result; }, ownKeys(target) { const result = Reflect.ownKeys(target); track(target, Symbol.iterator, OPERATORTYPES.TRACKER[Symbol.iterator]); return result; }, set(target, propKey, value, receiver) { const result = Reflect.set(target, propKey, value, receiver); let trrigerType = Reflect.has(target, propKey) ? OPERATORTYPES.TRRIGER.SET : OPERATORTYPES.TRRIGER.ADD; if (result) trriger(target, propKey, trrigerType); return result; }, deleteProperty(target, propKey) { const result = Reflect.deleteProperty(target, propKey); if (result) { const _target = targetMap.get(target); if (_target && _target.propMap && _target.propMap.has(propKey)) { trriger(target, propKey, OPERATORTYPES.TRRIGER.DELETE, true); _target.propMap.delete(propKey); } } return result; } }; function createProxy(target) { if (target instanceof Proxy) return target; if (target instanceof Array) return new Proxy(target, { ...reactiveOptions, get(target$1, propKey, receiver) { track(target$1, propKey, OPERATORTYPES.TRACKER.GET); const value = Reflect.get(target$1, propKey, receiver); if (isObject(value)) return reactive(value); if (typeof value === "function") { const rerwites = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse"]; if (rerwites.includes(propKey)) return function (...args) { const result = value.apply(this ?? receiver, args); trriger(target$1, Symbol.iterator, OPERATORTYPES.TRRIGER.SET); return result; }; return value.bind(receiver); } return value; } }); if (target instanceof Set) return new Proxy(target, { ...reactiveOptions, get(target$1, propKey, receiver) { track(target$1, propKey, OPERATORTYPES.TRACKER.GET); const value = Reflect.get(target$1, propKey, receiver); if (isObject(value)) return reactive(value); if (typeof value === "function") return value.bind(receiver); return value; } }); if (target instanceof WeakSet) return new Proxy(target, reactiveOptions); if (target instanceof Map) return new Proxy(target, { ...reactiveOptions, get(target$1, propKey, receiver) { track(target$1, propKey, OPERATORTYPES.TRACKER.GET); const value = Reflect.get(target$1, propKey, receiver); if (isObject(value)) return reactive(value); if (typeof value === "function") { if (propKey === "get") return function (key) { const result = value.apply(this ?? receiver, [key]); track(target$1, key, OPERATORTYPES.TRACKER.GET); return result; }; if (propKey === "set") return function (key, newValue) { const result = value.call(this ?? receiver, key, newValue); trriger(target$1, key, OPERATORTYPES.TRRIGER.SET); return result; }; return function (...args) { const result = value.apply(this ?? receiver, args); return result; }; } return value; } }); if (target instanceof WeakMap) return new Proxy(target, reactiveOptions); return new Proxy(target, reactiveOptions); } function reactive(target) { let _target = targetMap.get(target); if (!_target) { _target = { propMap: /* @__PURE__ */new Map(), proxy: createProxy(target) }; targetMap.set(target, _target); } if (!_target.proxy) _target.proxy = createProxy(target); return _target.proxy; } //#endregion //#region src/Computed/index.ts var Computed = class { constructor(props) { Object.assign(this, props); this.dirty = true; } track() { const running = getGlobalData("@msom/reaction"); if (running?.tracking) running.tracking(this); } get() { this.track(); if (!this.dirty && true) return this.cache;else { this.compute(); return this.cache; } } compute() { const { method } = this; if (!method) return; this.subReaction?.destroy(); this.subReaction = createReaction(() => { this.cache = method(); this.dirty = false; }, () => { this.notify(); }); } notify() { this.dirty = true; const handles = [...this.handles]; for (const reaction of handles) reaction.notify(); } addReaction(reaction) { this.handles.add(reaction); } removeReaction(reaction) { this.handles.delete(reaction); } destroy() { this.dirty = true; this.subReaction?.destroy(); this.subReaction = void 0; this.handles.forEach((reaction) => { reaction.removeObserver(this); }); } }; //#endregion //#region src/utils.ts const observerMapSymbolKey = Symbol("observerMapSymbolKey"); function getObserver(key) { const observersMap = Reflect.get(this, observerMapSymbolKey, this); if (!observersMap) return; const observer$1 = observersMap.get(key); if (!observer$1) return; return observer$1; } const _observer = "observer"; const _computed = "computed"; const observerTypeMap = { [_observer]: Observer, [_computed]: Computed }; function generateIObserver(key, type, option) { const observersMap = Reflect.get(this, observerMapSymbolKey, this) || /* @__PURE__ */new Map(); Reflect.set(this, observerMapSymbolKey, observersMap, this); const observer$1 = observersMap.get(key) || new observerTypeMap[type](option); observersMap.set(key, observer$1); return observer$1; } //#endregion //#region src/decorators/observer.ts function observer(option = {}) { return function (target, key) { if (typeof target === "function") throw new ObserverDecoratorUsedError({ NotStatic: true }); const descriptor = Object.getOwnPropertyDescriptor(target, key); if (descriptor) { if (typeof descriptor.value === "function") throw new ObserverDecoratorUsedError({ NotMethod: true }); throw new ObserverDecoratorUsedError({ NotAccessor: true }); } if (isComponent(target)) { const definition = initComponentDefinition(target); if (Reflect.has(definition.$observers, key)) throw new ObserverDecoratorUsedError({ defineMessage() { return `the observer property ${String(key)} is exist.`; } }); defineProperty(definition.$observers, key, 7, "observer"); } defineAccesser(target, key, 5, function () { return generateIObserver.bind(this)(key, _observer, option).get(); }, function (value) { return generateIObserver.bind(this)(key, _observer, option).set(value); }); }; } //#endregion //#region src/decorators/computed.ts /** * 计算属性装饰器 * 用于get计算属性和无参函数上 * @returns MethodDecorator */ function computed(option) { return function (target, key, descriptor) { if (typeof target === "function") throw new ComputedDecoratorUsedError({ NotStatic: true }); if (!descriptor) throw new ComputedDecoratorUsedError({ NotProperty: true }); let method = descriptor.value; let methodType = "value"; if (typeof method !== "function") { method = descriptor.get; methodType = "get"; } if (typeof method !== "function") throw new ComputedDecoratorUsedError({ defineMessage: () => { return `the method or accessor for getter ${String(key)} not be function.`; } }); if (method.length > 0) { console.warn(new ComputedDecoratorUsedError({ defineMessage: () => { return "the computed effct method should be no argument"; } }).message); return; } if (isComponent(target)) { const definition = initComponentDefinition(target); if (Reflect.has(definition.$observers, key) && definition.$observers[key]) throw new ComputedDecoratorUsedError({ defineMessage: () => { return `the computed method or computed property ${String(key)} is exist.`; } }); defineProperty(definition.$observers, key, 7, "computed"); } const _method = method; descriptor[methodType] = function () { const create = generateIObserver.bind(this); return create(key, _computed, { ...option, method: _method.bind(this) }).get(); }; const setter = descriptor.set; if (setter) descriptor.set = function (value) { setter.call(this, value); const create = generateIObserver.bind(this); create(key, _computed, { ...option, method: _method.bind(this) }).notify(); }; }; } //#endregion export { Computed, Observer, Reaction, computed, createReaction, getObserver, observer, reactive, withoutTrack }; //# sourceMappingURL=index.js.map