diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index 791c79766..989c0f663 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.ts @@ -398,11 +398,11 @@ export class BuiltinSimulatorHost implements ISimulatorHost void) { + onActivityEvent(cb: (activity: ActivityData, ctx?: any) => void) { this.emitter.on('activity', cb); return () => { this.emitter.off('activity', cb); @@ -439,7 +439,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost { // console.log('add node', node); diff --git a/packages/react-simulator-renderer/src/renderer.ts b/packages/react-simulator-renderer/src/renderer.ts index 03adc42f1..071136208 100644 --- a/packages/react-simulator-renderer/src/renderer.ts +++ b/packages/react-simulator-renderer/src/renderer.ts @@ -18,8 +18,7 @@ import { getProjectUtils, applyActivities, } from '@ali/lowcode-utils'; - -import { RootSchema, ComponentSchema, TransformStage, NodeSchema, ActivityData } from '@ali/lowcode-types'; +import { RootSchema, ComponentSchema, TransformStage, NodeSchema, ActivityType, ActivityData } from '@ali/lowcode-types'; // just use types import { BuiltinSimulatorRenderer, NodeInstance, Component, DocumentModel } from '@ali/lowcode-designer'; import LowCodeRenderer from '@ali/lowcode-react-renderer'; @@ -27,9 +26,11 @@ import { createMemoryHistory, MemoryHistory } from 'history'; import Slot from './builtin-components/slot'; import Leaf from './builtin-components/leaf'; import { withQueryParams, parseQuery } from './utils/url'; +import { supportsQuickPropSetting, getUppermostPropKey, setInstancesProp } from './utils/misc'; const loader = new AssetLoader(); +const FULL_RENDER_THRESHOLD = 1000; export class DocumentInstance { - private instancesMap = new Map(); + public instancesMap = new Map(); @obx.ref private _schema?: RootSchema; @computed get schema(): any { @@ -39,12 +40,33 @@ export class DocumentInstance { private disposeFunctions: Array<() => void> = []; constructor(readonly container: SimulatorRendererContainer, readonly document: DocumentModel) { - this.disposeFunctions.push(host.autorun(() => { + const documentExportDisposer = host.autorun(() => { this._schema = document.export(TransformStage.Render); - })); - this.disposeFunctions.push(host.onActivityEvent((data: ActivityData) => { - if (host.mutedActivityEvent) return; - this._schema = applyActivities(this._schema!, data); + }); + this.disposeFunctions.push(documentExportDisposer); + let tid: NodeJS.Timeout; + this.disposeFunctions.push(host.onActivityEvent((data: ActivityData, ctx: any) => { + if (host.mutedActivityEvent || (ctx && ctx.doc !== this.document)) return; + + if (tid) clearTimeout(tid); + // 临时关闭全量计算 schema 的逻辑,在增量计算结束后,来一次全量计算 + documentExportDisposer.$obx.sleep(); + if (data.type === ActivityType.MODIFIED) { + // 对于修改场景,优先判断是否能走「快捷设置」逻辑 + if (supportsQuickPropSetting(data, this)) { + setInstancesProp(data, this); + } else { + this._schema = applyActivities(this._schema!, data); + } + } else if (data.type === ActivityType.ADDED) { + // FIXME: 待补充 节点增加 逻辑 + } else if (data.type === ActivityType.DELETED) { + // FIXME: 待补充 节点删除 逻辑 + } else if (data.type === ActivityType.COMPOSITE) { + // FIXME: 待补充逻辑 + } + + tid = setTimeout(() => documentExportDisposer.$obx.wakeup(true), FULL_RENDER_THRESHOLD); // TODO: 调试增量模式,打开以下代码 // this._deltaData = data; // this._deltaMode = true; diff --git a/packages/react-simulator-renderer/src/utils/misc.ts b/packages/react-simulator-renderer/src/utils/misc.ts index ea6f5147e..3ee2af033 100644 --- a/packages/react-simulator-renderer/src/utils/misc.ts +++ b/packages/react-simulator-renderer/src/utils/misc.ts @@ -1,3 +1,7 @@ +import { ReactInstance } from 'react'; +import { ActivityData } from '@ali/lowcode-types'; +import { DocumentInstance } from '../renderer'; + interface UtilsMetadata { name: string; npm: { @@ -23,4 +27,69 @@ export function getProjectUtils(librayMap: LibrayMap, utilsMetadata: UtilsMetada } }); } +} + +/** + * 获取最靠近 Props / PropStash 的 prop 实例 + * @param prop + * @returns + */ +export function getUppermostPropKey(prop: any): string { + let curProp = prop; + while (curProp.parent.constructor.name !== 'Props' && curProp.parent.constructor.name !== 'PropStash') { + curProp = curProp.parent; + } + return curProp.key; +} + +function haveForceUpdate(instances: any[]): boolean { + return instances.every(inst => 'forceUpdate' in inst); +} + +/** + * 是否支持快捷属性值设值 + * @param data + * @param doc + * @returns + */ +export function supportsQuickPropSetting(data: ActivityData, doc: DocumentInstance) { + const { payload } = data; + const { schema, prop } = payload; + const nodeId = schema.id!; + // const key = data.payload.prop.key; + const instances = doc.instancesMap.get(nodeId); + const uppermostPropKey = getUppermostPropKey(prop); + + return ( + nodeId && + Array.isArray(instances) && + instances.length > 0 && + haveForceUpdate(instances) && + uppermostPropKey && + !uppermostPropKey.startsWith('___') + ); +} + +/** + * 设置属性值 + * @param data + * @param doc + */ +export function setInstancesProp(data: ActivityData, doc: DocumentInstance) { + const { payload } = data; + const { schema, prop, newValue } = payload; + const nodeId = schema.id!; + const instances = doc.instancesMap.get(nodeId)!; + const propKey = getUppermostPropKey(prop); + let value = (schema.props as any)[propKey]; + // 当 prop 是在 PropStash 中产生时,该 prop 需要在下一个 obx 的时钟周期才能挂载到相应位置, + // 而 schema 是同步 export 得到的,此时 schema 中还没有对应的值,所以直接取 newValue + if (prop.parent.constructor.name === 'PropStash') { + value = newValue; + } + + instances.forEach((inst: any) => { + inst.props[propKey] = value; + inst.forceUpdate(); + }); } \ No newline at end of file diff --git a/packages/vision-polyfill/src/pages.ts b/packages/vision-polyfill/src/pages.ts index ff271ebc0..006ea9702 100644 --- a/packages/vision-polyfill/src/pages.ts +++ b/packages/vision-polyfill/src/pages.ts @@ -63,6 +63,7 @@ const pages = Object.assign(project, { item.methods = {}; } }); + project.load( { version: '1.0.0', @@ -73,7 +74,6 @@ const pages = Object.assign(project, { }, true, ); - // FIXME: 根本原因是 PropStash 导致的,在页面节点初始化结束后,hideModalNodes 导致了第一次变化 // 这样可以避免页面加载之后就被标记为 isModified setTimeout(() => {