@msom/reaction
Version:
@msom/reaction
535 lines (524 loc) • 16.3 kB
JavaScript
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