UNPKG

dehub

Version:
413 lines (412 loc) 15.4 kB
"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;