dehub
Version:
Data&Event MessageHub.
413 lines (412 loc) • 15.4 kB
JavaScript
"use strict";
/**
* DEComp 模块
* 这是一个组件管理模块,负责组件的生命周期、状态管理和事件处理
* @module DEComp
*/
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("lodash");
const DEHub_1 = require("./DEHub");
const DETypes_1 = require("./DETypes");
/** 全局组件映射表 */
const compMap = new Map();
/** 状态变更事件处理函数映射表 */
const onStateChangingMap = new Map();
/** 组件就绪等待器映射表 */
const onCompReadyWaitersMap = new Map();
/** 脏状态映射表 */
const dirtyStateMap = new Map();
/** 组件就绪状态映射表 */
const compReadMap = new Map();
/**
* 组件类
* 实现了 IDEComp 接口,提供组件的状态管理和生命周期功能
*/
class DEComp {
/** 组件标签 */
#tag;
/** 最后更新时间 */
#updateOn;
/** sessionStorage 写入防抖定时器 */
#sessionSaveTimerId;
/** 是否已销毁 */
#destroyed = false;
constructor(tag, options, state) {
if (tag instanceof DETypes_1.Tag) {
this.#tag = tag;
}
else {
this.#tag = new DETypes_1.Tag(tag);
}
if (!onStateChangingMap.has(this.tag.path)) {
onStateChangingMap.set(this.tag.path, []);
}
if (!onCompReadyWaitersMap.has(this.tag.path)) {
onCompReadyWaitersMap.set(this.tag.path, []);
}
if (!dirtyStateMap.has(this.tag.path)) {
dirtyStateMap.set(this.tag.path, {});
}
if (!compReadMap.has(this.tag.path)) {
compReadMap.set(this.tag.path, false);
}
this.#updateOn = performance.now();
if (options) {
if (options.autoReady === undefined) {
options.autoReady = true;
}
if (options.useSession === undefined) {
options.useSession = false;
}
}
let stateObj;
if (options?.useSession === true) {
const sessionState = this.#loadSessionState();
if (sessionState && sessionState.state) {
stateObj = sessionState.state;
}
}
stateObj = (0, lodash_1.merge)(stateObj ?? {}, state ?? {});
if (!compMap.has(this.tag.path)) {
this.compStore = {
options: options ?? { autoReady: true, useSession: false },
state: stateObj
};
this.#mounting().catch((err) => {
(0, DEHub_1.error)(this, err);
});
}
}
get compStore() {
return compMap.get(this.tag.path);
}
set compStore(state) {
compMap.set(this.tag.path, state);
if (this.options.useSession) {
// 防抖写入 sessionStorage,避免频繁同步 I/O
if (this.#sessionSaveTimerId)
clearTimeout(this.#sessionSaveTimerId);
this.#sessionSaveTimerId = setTimeout(() => {
try {
sessionStorage.setItem(this.tag.path, JSON.stringify(this.compStore));
}
catch (e) {
console.error('[DEComp] sessionStorage write failed:', e);
}
}, 300);
}
}
get onStateChangingHandler() {
return onStateChangingMap.get(this.tag.path);
}
get onReadyWaiters() {
return onCompReadyWaitersMap.get(this.tag.path);
}
get dirtyState() {
return dirtyStateMap.get(this.tag.path);
}
set dirtyState(dirty) {
dirtyStateMap.set(this.tag.path, dirty);
}
#mounting = async () => {
let preContext = {
sender: this,
event: DETypes_1.EventNames.Mounting,
stage: DETypes_1.EventStage.PreOperation,
detail: { ...this.compStore, tag: this.tag }
};
const [context, prePromise] = (0, DEHub_1.emit)(preContext);
const prePromiseResults = await Promise.allSettled(prePromise);
(0, DETypes_1.rejectedCheck)(prePromiseResults);
if (context.cancel === true) {
throw Error(`Component loading is cancelled`);
}
if (context.output.state) {
this.compStore.state = (0, DETypes_1.mergeObject)(this.compStore.state, context.output.state);
}
let emitPostContext = { ...preContext, sender: this, stage: DETypes_1.EventStage.PostOperation };
if (this.options.autoReady === undefined || this.options.autoReady === true) {
await this.setReady(true);
}
const [postContext, postPromises] = (0, DEHub_1.emit)(emitPostContext);
if (this.options.useSession) {
this.#flushSessionStorage();
}
if (postPromises && postPromises.length > 0) {
await Promise.allSettled(postPromises);
}
};
#loadSessionState = () => {
const stateImage = sessionStorage.getItem(this.tag.path);
if (!stateImage) {
return { state: {}, options: this.options };
}
try {
return JSON.parse(stateImage);
}
catch (e) {
(0, DEHub_1.error)(this, Error(`${this.tag.path}组件从存储中还原状态失败。Image:${stateImage}`));
return { state: {}, options: this.options };
}
};
#flushSessionStorage = () => {
if (this.#sessionSaveTimerId)
clearTimeout(this.#sessionSaveTimerId);
try {
sessionStorage.setItem(this.tag.path, JSON.stringify(this.compStore));
}
catch (e) {
console.error('[DEComp] sessionStorage flush failed:', e);
}
};
get options() {
return this.compStore?.options ?? { autoReady: true, useSession: false };
}
set options(options) {
if (!this.compStore) {
this.compStore = {
options: options ?? { autoReady: true, useSession: false },
state: {}
};
}
else {
this.compStore.options = options;
}
}
waitReady = () => {
return new Promise((resolve, reject) => {
if (this.isReady === true) {
resolve();
return;
}
const promiseGroup = this.onReadyWaiters;
promiseGroup?.push([resolve, reject]);
});
};
#resolveReadyCallbacks = () => {
const promiseGroup = this.onReadyWaiters;
if (promiseGroup.length === 0)
return;
for (const promiseItem of promiseGroup) {
const resolve = promiseItem[0];
resolve();
}
onCompReadyWaitersMap.set(this.tag.path, []);
};
setReady = (isReady) => {
return new Promise((resolve, reject) => {
if (this.isReady === isReady) {
if (isReady === true) {
this.#resolveReadyCallbacks();
}
return resolve();
}
const preValue = this.isReady;
this.isReady = isReady;
if (isReady === true) {
this.#resolveReadyCallbacks();
}
const emitContext = {
sender: this,
event: DETypes_1.EventNames.StateChanging,
stage: DETypes_1.EventStage.PostOperation,
detail: {
value: isReady,
preImage: preValue,
postImage: isReady
},
property: '$isReady'
};
try {
const [outputContext, outputPromises] = (0, DEHub_1.emit)(emitContext);
Promise.allSettled(outputPromises).then((results) => {
(0, DETypes_1.rejectedCheck)(results);
resolve();
}).catch((reason) => {
reject(reason);
});
}
catch (err) {
(0, DEHub_1.error)(this, err);
reject(err);
}
});
};
get isReady() {
return compReadMap.get(this.tag.path) ?? false;
}
set isReady(value) {
compReadMap.set(this.tag.path, value);
}
get tag() {
return this.#tag;
}
get state() {
// 移除每3秒轮询 sessionStorage 的逻辑,避免同步 I/O 阻塞
return (this.compStore.state ?? {});
}
onStateChanging(callback) {
if (this.onStateChangingHandler.indexOf(callback) >= 0) {
return;
}
this.onStateChangingHandler.push(callback);
}
removeHandler(callback) {
const index = this.onStateChangingHandler.indexOf(callback);
if (index < 0) {
return;
}
this.onStateChangingHandler.splice(index, 1);
}
#emitOnStateChanged = (eventContext) => {
if (this.onStateChangingHandler.length > 0) {
for (const func of this.onStateChangingHandler) {
try {
func.call(this, eventContext);
}
catch (err) {
(0, DEHub_1.error)(this, err);
}
}
}
};
#updateVer = 0;
#stateVer = new Map();
update = (target, deep = false) => {
const updateVer = this.#updateVer++;
return new Promise(async (resolve, reject) => {
const targetKeys = Object.keys(target);
const updateTarget = {};
const imageState = {};
for (const k of targetKeys) {
imageState[k] = this.state[k];
if ((0, lodash_1.isEqual)(imageState[k], target[k])) {
continue;
}
updateTarget[k] = target[k];
this.#stateVer.set(k, updateVer);
}
let dirtyState = { ...this.dirtyState };
let outResult = (0, DETypes_1.mergeObject)(imageState, updateTarget, deep);
const tempState = (0, DETypes_1.mergeObject)(imageState, dirtyState, deep);
if ((0, lodash_1.isEqual)(outResult, tempState)) {
return resolve({});
}
dirtyState = (0, lodash_1.merge)(dirtyState ?? {}, updateTarget);
const preImage = { ...this.state };
this.dirtyState = dirtyState;
const idPath = this.#tag.path;
const updateKeys = Object.keys(updateTarget);
let lastPreContext = null;
for (const property of updateKeys) {
const emitContext = {
sender: this,
event: DETypes_1.EventNames.StateChanging,
stage: DETypes_1.EventStage.PreOperation,
detail: {
value: updateTarget[property],
preImage: preImage[property],
postImage: outResult[property]
},
property
};
try {
const [outputContext, outputPromises] = (0, DEHub_1.emit)(emitContext);
if (!lastPreContext) {
lastPreContext = outputContext;
}
if (outputContext.cancel === true) {
return reject([outputContext, outputPromises]);
}
if (outputPromises && outputPromises.length > 0) {
const asyncResult = await Promise.allSettled(outputPromises);
(0, DETypes_1.rejectedCheck)(asyncResult);
}
}
catch (err) {
(0, DEHub_1.error)(this, err);
return reject(err);
}
}
const stateValue = (0, DETypes_1.clearNullNodes)((0, DETypes_1.mergeObject)(this.compStore.state, outResult));
for (const k of targetKeys) {
if (updateVer === this.#stateVer.get(k)) {
if (this.dirtyState[k] !== undefined) {
delete this.dirtyState[k];
}
}
else {
if (this.state[k] !== undefined) {
stateValue[k] = this.state[k];
}
}
}
this.compStore.state = stateValue;
if (this.options.useSession === true) {
this.#flushSessionStorage();
this.#updateOn = performance.now();
}
if (this.onStateChangingHandler && this.onStateChangingHandler.length > 0 && lastPreContext) {
this.#emitOnStateChanged({
...lastPreContext,
sender: this,
detail: {
value: updateTarget,
preImage: preImage,
postImage: outResult
},
property: Object.keys(updateTarget)
});
}
for (const property of updateKeys) {
const emitContext = {
sender: this,
event: DETypes_1.EventNames.StateChanging,
stage: DETypes_1.EventStage.PostOperation,
detail: {
value: updateTarget[property],
preImage: preImage[property],
postImage: outResult[property]
},
property
};
try {
const [postResult, postPromises] = (0, DEHub_1.emit)(emitContext);
if (postPromises.length > 0) {
const asyncResults = await Promise.allSettled(postPromises);
(0, DETypes_1.rejectedCheck)(asyncResults);
}
}
catch (err) {
(0, DEHub_1.error)(this, err);
reject(err);
return;
}
}
return resolve(updateTarget);
});
};
destroy = async () => {
if (this.#destroyed)
return;
this.#destroyed = true;
if (this.#sessionSaveTimerId)
clearTimeout(this.#sessionSaveTimerId);
try {
await (0, DEHub_1.delComponent)(this);
}
catch (e) { /* ignore */ }
// 清理所有关联的 Map 状态
compMap.delete(this.tag.path);
onStateChangingMap.delete(this.tag.path);
onCompReadyWaitersMap.delete(this.tag.path);
dirtyStateMap.delete(this.tag.path);
compReadMap.delete(this.tag.path);
// 清理 sessionStorage
try {
sessionStorage.removeItem(this.tag.path);
}
catch (e) { /* ignore */ }
};
}
exports.default = DEComp;