UNPKG

@msom/reaction

Version:

@msom/reaction

897 lines (885 loc) 26.5 kB
//#region ../common/dist/index.js function assert(condition, message = "") { if (!condition) throw Error(message); } /** * 集合类,提供基于键值的元素存储和管理 * @template T 元素类型 */ var Collection = class { /** * 创建集合实例 * @param getKey 获取元素键值的函数 */ constructor(getKey) { assert(getKey, "miss get unique key"); this.getKey = getKey; this.elements = new Array(); this.elMap = /* @__PURE__ */new Map(); this.indexMap = /* @__PURE__ */new Map(); } /** * 获得集合的实际元素数量 */ size() { return Reflect.ownKeys(this.elements).length; } /** * 根据键值获取元素 * @param key 元素的键值 * @returns 对应的元素或undefined */ get(key) { return this.elMap.get(key); } /** * 检查是否存在指定键值的元素 * @param key 要检查的键值 * @returns 是否存在 */ hasKey(key) { return this.elMap.has(key); } /** * 检查元素是否在集合中 * @param element 要检查的元素 * @returns 是否存在 */ hasElement(element) { return this.hasKey(this.getKey(element)); } /** * 添加元素到集合 * @param element 要添加的元素 * @param force 当元素已存在时是否强制替换,默认为false */ add(element, force) { const key = this.getKey(element); const has = this.elMap.has(key); if (!has) { const index = this.elements.push(element) - 1; this.indexMap.set(key, index); this.elMap.set(key, element); } else if (force) { const index = this.indexMap.get(key); assert(index); this.elMap.set(key, element); this.elements.splice(index, 1, element); } } /** * 使用指定的键值添加元素 * @param key 指定的键值 * @param element 要添加的元素 * @param force 当键值已存在时是否强制替换,默认为false */ addKey(key, element, force) { const has = this.elMap.has(key); if (!has) { const index = this.elements.push(element) - 1; this.indexMap.set(key, index); this.elMap.set(key, element); } else if (force) { const index = this.indexMap.get(key); assert(index != void 0); this.elMap.set(key, element); this.elements.splice(index, 1, element); } } /** * 批量添加元素 * @param iterator 可迭代的元素集合 * @param force 当元素已存在时是否强制替换,默认为false */ addAll(iterator$1, force) { const { next } = iterator$1[Symbol.iterator](); let result = next(); while (!result.done) { this.add(result.value, force); result = next(); } } /** * 在指定位置插入元素 * @param element 待插入的元素 * @param index 插入位置,范围[0, length]。如果超出范围会被自动调整到有效范围内 * @param exist 当元素已存在时的处理选项 * @param exist.index 是否保持原有元素的位置。true: 保持原位置,false: 使用新位置 * @param exist.element 是否使用新元素替换原有元素。true: 使用新元素,false: 保持原有元素 */ insert(element, index, exist) { const key = this.getKey(element); const has = this.elMap.has(key); const { index: cIndex, element: cElement } = exist || {}; if (!has) { this.elMap.set(key, element); index = Math.min(this.elements.length, Math.max(0, index)); this.elements.splice(index, 0, element); } else { const oIndex = this.indexMap.get(key); assert(oIndex); const oElement = this.elements[oIndex]; const placeholder = Symbol("placegholder"); this.elements[oIndex] = placeholder; if (!cIndex) index = oIndex; if (!cElement) element = oElement; this.elements.splice(index, 0, element); this.elements = this.elements.filter((v) => v !== placeholder); this.updateIndexMap(); } } /** * 移除指定元素 * @param element 要移除的元素 * @returns 是否成功移除 */ removeElement(element) { const key = this.getKey(element); return !!this.remove(key); } /** * 根据键值移除元素 * @param key 要移除的元素的键值 * @returns 被移除的元素,如果元素不存在则返回undefined */ remove(key) { const has = this.elMap.has(key); if (has) { const index = this.indexMap.get(key); assert(index); this.elements.splice(index, 1); this.updateIndexMap(); this.elMap.delete(key); } return void 0; } /** * 清空集合中的所有元素 */ clear() { this.elMap.clear(); this.elements.length = 0; this.indexMap.clear(); } /** * 更新索引映射 * 当元素数组发生变化时,需要重新计算每个元素的索引 * @private */ updateIndexMap() { const { elements, indexMap } = this; const { length } = elements; indexMap.clear(); for (let i = 0; i < length; i++) { const element = elements[i]; const key = this.getKey(element); indexMap.set(key, i); } } /** * 实现Iterable接口,使集合可以被迭代 * 使用生成器函数遍历集合中的所有元素 * @yields 集合中的每个元素 */ [Symbol.iterator]() { let i = 0; return { next: () => { const j = i; i++; return { value: this.elements[j], done: j >= this.elements.length }; } }; } /** * 遍历集合中的所有元素 * @param handler 处理每个元素的回调函数 */ each(handler) { this.elMap.forEach((el, k) => handler(el, k, this)); } toArray(filter, mapper) { const result = []; const elements = this.elements; for (let i = 0; i < elements.length; i++) { const element = elements[i]; const key = this.getKey(element); if (filter && !filter(element, key, this)) continue; const value = mapper ? mapper(element, key, this) : element; result.push(value); } return result; } }; const symbolKeys = new Collection((keys) => keys.key); function GeneratSymbolKey(key) { if (symbolKeys.hasKey(key)) return symbolKeys.get(key).symbolKey;else { const symbolKey = Symbol(key); symbolKeys.add({ key, symbolKey }); return symbolKey; } } function setGlobalData(key, data) { const symbolKey = GeneratSymbolKey(key); Object.assign(globalThis, { [symbolKey]: data }); return data; } function getGlobalData(key) { const symbolKey = GeneratSymbolKey(key); const data = Reflect.get(globalThis, symbolKey); if (!data) { if (key.startsWith("@msom/")) throw `The GlobalData of ${key} is must init before get.`; return setGlobalData(key, {}); } return data; } const ENUMERABLE = 4; const WRITABLE = 2; const CONFIGURABLE = 1; /** * @param target * @param propKey * @param flag 7 * * const enumerable = 0x04; * * const writable = 0x02; * * const configurable = 0x01; * @param value */ function defineProperty(target, propKey, flag = 7, value) { Object.defineProperty(target, propKey, { value, writable: !!(WRITABLE & flag), enumerable: !!(ENUMERABLE & flag), configurable: !!(CONFIGURABLE & flag) }); } /** * @param target * @param propKey * @param flag 5 * * const enumerable = 0x04; * * const writable = 0x02; * * const configurable = 0x01; * * 访问器属性修饰符无法设置writable * @param getter * @param setter */ function defineAccesser(target, propKey, flag = 5, getter, setter) { Object.defineProperty(target, propKey, { enumerable: !!(ENUMERABLE & flag), configurable: !!(CONFIGURABLE & flag), get: getter, set: setter }); } function equal(value, otherValue) { return Object.is(value, otherValue); } const EVENTS = Symbol("__EVENTS__"); const FRAME_INTERVAL = 1e3 / 60; const TYPE_SPLITOR = ","; const OVERlOAD_KEY = Symbol("overload"); const ADD_IMPLEMENT = "addImplement"; /** * 创建一个可重载的函数 * @template T 类型数组的数组,每个数组最后一个类型为返回值类型 */ function createOverload(impls) { const overloadCollection = new Collection((m) => { return Reflect.get(m, OVERlOAD_KEY); }); const Method = { method(...args) { const overloadKey = args.map((v) => typeof v).join(TYPE_SPLITOR); const overload = overloadCollection.get(overloadKey); assert(overload, "No implementation found"); return overload.apply(this, args); }, add(...impl) { const overload = impl.pop(); if (typeof overload !== "function") throw Error("The last parameter must be a function"); const overloadKey = impl.join(TYPE_SPLITOR); overloadCollection.addKey(overloadKey, overload, true); } }; defineProperty(Method.method, ADD_IMPLEMENT, 0, Method.add); if (impls) for (const impl of impls) Method.add(...impl); return Method.method; } /** * 使用示例 */ const example = createOverload([ [ "string", "number", (a, c = 1) => Number(a) + c], ["string", (a) => a], ["number", (a) => a]] ); example("1", 2); example("1"); example(1); example[ADD_IMPLEMENT]("string", "number", (a, c = 1) => Number(a) + c); const onlyUsedMap = { observer: "observer decorator only be used with instance property.", option: "option decorator only be used with instance property or accessor property for setter.", component: "component decorator only be used with class.", computed: "computed decorator only be used with instance method or accessor property for getter." }; function decoratorUsedErrorOptionHandler(decoratorName, option) { const { defineMessage } = option; if (defineMessage) return typeof defineMessage === "function" ? defineMessage() : defineMessage; const should = [ "(", onlyUsedMap[decoratorName], ")"]; let notIndex = ""; if (option.NotStatic) notIndex = "static property or method";else if (option.NotInComponent) notIndex = "outside a Component";else if (option.NotSetter) notIndex = "accessor property for not setter";else if (option.NotMethod) notIndex = "a instance method";else if (option.NotAccessor) notIndex = "accessor property";else if (option.NotClass) notIndex = "not a class";else if (option.NotProperty) notIndex = "a instance property"; return `${notIndex ? `not allow used with ${notIndex}.` : ""} ${notIndex ? should.join("") : should[1]}`.trim(); } /** * class: ObserverDUE */ var ObserverDUE = class extends Error { constructor(option = {}) { super(decoratorUsedErrorOptionHandler("observer", option)); } }; const ObserverDecoratorUsedError = ObserverDUE; /** * class: ComputedDUE */ var ComputedDUE = class extends Error { constructor(option = {}) { super(decoratorUsedErrorOptionHandler("computed", option)); } }; const ComputedDecoratorUsedError = ComputedDUE; function isObject(value) { return typeof value === "object" && value !== null; } const componentGlobalData = setGlobalData("@msom/component", { componentDefinitionKey: Symbol("component_definition"), componentMap: /* @__PURE__ */new Map() }); /** * 初始化组件定义 * 不会向上继续找原型对象的原型 * @param prototype 组件类或类的原型对象 * @returns 组件定义 */ function initComponentDefinition(prototype) { const { componentDefinitionKey } = componentGlobalData; prototype = typeof prototype === "function" ? prototype.prototype : prototype; const prototype_prototype = Object.getPrototypeOf(prototype); const prototype_prototype_definition = getComponentDefinition(prototype_prototype); try { Object.setPrototypeOf(prototype, null); let definition = Reflect.get(prototype, componentDefinitionKey); if (!definition) { definition = Object.create(null); assert(definition); Object.assign(definition, { $options: Object.create(prototype_prototype_definition?.["$options"] || null), $events: Object.create(prototype_prototype_definition?.["$events"] || null), $observers: Object.create(prototype_prototype_definition?.["$observers"] || null) }); defineProperty(prototype, componentDefinitionKey, 0, definition); } return definition; } finally { Object.setPrototypeOf(prototype, prototype_prototype); } } /** * @param prototype 组件类或类的原型对象 * @returns 组件定义 */ function getComponentDefinition(prototype) { const { componentDefinitionKey } = componentGlobalData; prototype = typeof prototype === "function" ? prototype.prototype : prototype; const oldProptotype = Object.getPrototypeOf(prototype); try { Object.setPrototypeOf(prototype, null); return Reflect.get(prototype, componentDefinitionKey); } finally { Object.setPrototypeOf(prototype, oldProptotype); } } /** * 判断是否使用 @component 装饰器标记 * @param ctor 类构造器或原型对象 * @returns */ function isComponent(ctor) { const { componentDefinitionKey } = componentGlobalData; const target = typeof ctor === "function" ? ctor.prototype : ctor; const prototype = Object.getPrototypeOf(target); try { Object.setPrototypeOf(target, null); return Reflect.has(target, componentDefinitionKey); } finally { Object.setPrototypeOf(target, prototype); } } //#endregion //#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.equal = option.equal || equal; this.handlers = /* @__PURE__ */new Set(); if (Object.prototype.hasOwnProperty.call(option, "initValue")) { const value = option.initValue; assert(value); this.value = value; } } track() { const running = getGlobalData("@msom/reaction"); if (running?.tracking) running.tracking(this); } get() { this.track(); return this.value; } set(newValue) { const { value: oldValue } = this; this.value = newValue; if (!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); }); } }; 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()); 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); return isObject(value) ? reactive(value) : 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 reactive(target) { let _target = targetMap.get(target); if (!_target) { _target = { propMap: /* @__PURE__ */new Map(), proxy: new Proxy(target, reactiveOptions) }; targetMap.set(target, _target); } if (!_target.proxy) _target.proxy = new Proxy(target, reactiveOptions); 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