mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-15 02:38:16 +00:00
Merge branch 'feat/mobx-rendererPerformanceOptimization' into feat/0.16.12
This commit is contained in:
commit
2968e41ec4
@ -441,7 +441,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
|||||||
// TODO: Thinkof move events control to simulator renderer
|
// TODO: Thinkof move events control to simulator renderer
|
||||||
// just listen special callback
|
// just listen special callback
|
||||||
// because iframe maybe reload
|
// because iframe maybe reload
|
||||||
this.setupRendererChannel();
|
|
||||||
this.setupDragAndClick();
|
this.setupDragAndClick();
|
||||||
this.setupDetecting();
|
this.setupDetecting();
|
||||||
this.setupLiveEditing();
|
this.setupLiveEditing();
|
||||||
@ -452,81 +451,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
|||||||
this.emitter.emit(eventName, ...data);
|
this.emitter.emit(eventName, ...data);
|
||||||
}
|
}
|
||||||
|
|
||||||
onActivityEvent(cb: (activity: ActivityData, ctx?: any) => void) {
|
|
||||||
this.emitter.on('activity', cb);
|
|
||||||
return () => {
|
|
||||||
this.emitter.off('activity', cb);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
mutedActivityEvent: boolean = false;
|
|
||||||
muteActivityEvent() {
|
|
||||||
this.mutedActivityEvent = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
unmuteActivityEvent() {
|
|
||||||
this.mutedActivityEvent = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
runWithoutActivity(action: () => void) {
|
|
||||||
this.muteActivityEvent();
|
|
||||||
action();
|
|
||||||
this.unmuteActivityEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
setupRendererChannel() {
|
|
||||||
const { editor } = this.designer;
|
|
||||||
editor.on('node.innerProp.change', ({ node, prop, oldValue, newValue }) => {
|
|
||||||
// 在 Node 初始化阶段的属性变更都跳过
|
|
||||||
if (!node.isInited) return;
|
|
||||||
// 静音状态不触发事件,通常是非局部更新操作
|
|
||||||
if (this.mutedActivityEvent) return;
|
|
||||||
this.postEvent(
|
|
||||||
'activity',
|
|
||||||
{
|
|
||||||
type: 'modified',
|
|
||||||
payload: {
|
|
||||||
schema: node.export(TransformStage.Render, { bypassChildren: true }),
|
|
||||||
oldValue,
|
|
||||||
newValue,
|
|
||||||
prop,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ doc: this.currentDocument },
|
|
||||||
);
|
|
||||||
});
|
|
||||||
// editor.on('node.add', ({ node }) => {
|
|
||||||
// console.log('add node', node);
|
|
||||||
// this.postEvent('activity', {
|
|
||||||
// type: 'added',
|
|
||||||
// payload: {
|
|
||||||
// schema: node.export(TransformStage.Render),
|
|
||||||
// location: {
|
|
||||||
// parent: {
|
|
||||||
// nodeId: node.parent.id,
|
|
||||||
// index: node.index,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// editor.on('node.remove.topLevel', ({ node, index }) => {
|
|
||||||
// console.log('remove node', node);
|
|
||||||
// this.postEvent('activity', {
|
|
||||||
// type: 'deleted',
|
|
||||||
// payload: {
|
|
||||||
// schema: node.export(TransformStage.Render),
|
|
||||||
// location: {
|
|
||||||
// parent: {
|
|
||||||
// nodeId: node.parent.id,
|
|
||||||
// index,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
setupDragAndClick() {
|
setupDragAndClick() {
|
||||||
const { designer } = this;
|
const { designer } = this;
|
||||||
const doc = this.contentDocument!;
|
const doc = this.contentDocument!;
|
||||||
@ -1302,8 +1226,8 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
|||||||
const inst = instances
|
const inst = instances
|
||||||
? instances.length > 1
|
? instances.length > 1
|
||||||
? instances.find(
|
? instances.find(
|
||||||
(_inst) => this.getClosestNodeInstance(_inst, container.id)?.instance === containerInstance,
|
(_inst) => this.getClosestNodeInstance(_inst, container.id)?.instance === containerInstance,
|
||||||
)
|
)
|
||||||
: instances[0]
|
: instances[0]
|
||||||
: null;
|
: null;
|
||||||
const rect = inst
|
const rect = inst
|
||||||
|
|||||||
@ -178,10 +178,10 @@ export class ComponentMeta {
|
|||||||
this._title =
|
this._title =
|
||||||
typeof title === 'string'
|
typeof title === 'string'
|
||||||
? {
|
? {
|
||||||
type: 'i18n',
|
type: 'i18n',
|
||||||
'en-US': this.componentName,
|
'en-US': this.componentName,
|
||||||
'zh-CN': title,
|
'zh-CN': title,
|
||||||
}
|
}
|
||||||
: title;
|
: title;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,7 +429,7 @@ const builtinComponentActions: ComponentAction[] = [
|
|||||||
icon: IconHidden,
|
icon: IconHidden,
|
||||||
title: intlNode('hide'),
|
title: intlNode('hide'),
|
||||||
action(node: Node) {
|
action(node: Node) {
|
||||||
node.getExtraProp('hidden', true)?.setValue(true);
|
node.setVisible(false);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
condition: (node: Node) => {
|
condition: (node: Node) => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { obx, computed, makeObservable, runInAction } from '@ali/lowcode-editor-core';
|
import { obx, computed, makeObservable, runInAction } from '@ali/lowcode-editor-core';
|
||||||
import { IEditor, isJSExpression } from '@ali/lowcode-types';
|
import { GlobalEvent, IEditor, isJSExpression } from '@ali/lowcode-types';
|
||||||
import { uniqueId } from '@ali/lowcode-utils';
|
import { uniqueId } from '@ali/lowcode-utils';
|
||||||
import { SettingEntry } from './setting-entry';
|
import { SettingEntry } from './setting-entry';
|
||||||
import { Node } from '../../document';
|
import { Node } from '../../document';
|
||||||
@ -290,7 +290,7 @@ export class SettingPropEntry implements SettingEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notifyValueChange(oldValue: any, newValue:any) {
|
notifyValueChange(oldValue: any, newValue:any) {
|
||||||
this.editor.emit('node.prop.change', { node: this.getNode(), prop: this, oldValue, newValue });
|
this.editor.emit(GlobalEvent.Node.Prop.Change, { node: this.getNode(), prop: this, oldValue, newValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultValue() {
|
getDefaultValue() {
|
||||||
|
|||||||
@ -140,13 +140,8 @@ export class DocumentModel {
|
|||||||
this.history = new History(
|
this.history = new History(
|
||||||
() => this.export(TransformStage.Serilize),
|
() => this.export(TransformStage.Serilize),
|
||||||
(schema) => {
|
(schema) => {
|
||||||
if (this.simulator) {
|
this.import(schema as RootSchema, true);
|
||||||
this.simulator.runWithoutActivity(() => {
|
this.simulator?.rerender();
|
||||||
this.import(schema as RootSchema, true);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.import(schema as RootSchema, true);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -199,6 +194,13 @@ export class DocumentModel {
|
|||||||
return this._nodesMap.get(id) || null;
|
return this._nodesMap.get(id) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 id 获取节点
|
||||||
|
*/
|
||||||
|
getNodeCount(): number {
|
||||||
|
return this._nodesMap.size;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否存在节点
|
* 是否存在节点
|
||||||
*/
|
*/
|
||||||
@ -364,16 +366,7 @@ export class DocumentModel {
|
|||||||
if (node.isRoot()) return;
|
if (node.isRoot()) return;
|
||||||
this.internalRemoveAndPurgeNode(node, true);
|
this.internalRemoveAndPurgeNode(node, true);
|
||||||
});
|
});
|
||||||
// foreachReverse(this.rootNode?.children, (node: Node) => {
|
this.rootNode?.import(schema as any, checkId);
|
||||||
// this.internalRemoveAndPurgeNode(node, true);
|
|
||||||
// });
|
|
||||||
if (this.designer.project.simulator) {
|
|
||||||
this.designer.project.simulator.runWithoutActivity(() => {
|
|
||||||
this.rootNode?.import(schema as any, checkId);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.rootNode?.import(schema as any, checkId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: select added and active track added
|
// todo: select added and active track added
|
||||||
if (drillDownNodeId) {
|
if (drillDownNodeId) {
|
||||||
|
|||||||
@ -7,6 +7,11 @@ import { EventEmitter } from 'events';
|
|||||||
import { foreachReverse } from '../../utils/tree';
|
import { foreachReverse } from '../../utils/tree';
|
||||||
import { NodeRemoveOptions } from '../../types';
|
import { NodeRemoveOptions } from '../../types';
|
||||||
|
|
||||||
|
export interface IOnChangeOptions {
|
||||||
|
type: string;
|
||||||
|
node: Node;
|
||||||
|
}
|
||||||
|
|
||||||
export class NodeChildren {
|
export class NodeChildren {
|
||||||
@obx.shallow private children: Node[];
|
@obx.shallow private children: Node[];
|
||||||
|
|
||||||
@ -117,6 +122,10 @@ export class NodeChildren {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.children.splice(i, 1);
|
this.children.splice(i, 1);
|
||||||
|
this.emitter.emit('change', {
|
||||||
|
type: 'unlink',
|
||||||
|
node,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -150,7 +159,10 @@ export class NodeChildren {
|
|||||||
document.unlinkNode(node);
|
document.unlinkNode(node);
|
||||||
document.selection.remove(node.id);
|
document.selection.remove(node.id);
|
||||||
document.destroyNode(node);
|
document.destroyNode(node);
|
||||||
this.emitter.emit('change');
|
this.emitter.emit('change', {
|
||||||
|
type: 'delete',
|
||||||
|
node,
|
||||||
|
});
|
||||||
if (useMutator) {
|
if (useMutator) {
|
||||||
this.reportModified(node, this.owner, { type: 'remove', propagated: false, isSubDeleting: this.owner.isPurging, removeIndex: i, removeNode: node });
|
this.reportModified(node, this.owner, { type: 'remove', propagated: false, isSubDeleting: this.owner.isPurging, removeIndex: i, removeNode: node });
|
||||||
}
|
}
|
||||||
@ -197,7 +209,10 @@ export class NodeChildren {
|
|||||||
children.splice(index, 0, node);
|
children.splice(index, 0, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emitter.emit('change');
|
this.emitter.emit('change', {
|
||||||
|
type: 'insert',
|
||||||
|
node,
|
||||||
|
});
|
||||||
this.emitter.emit('insert', node);
|
this.emitter.emit('insert', node);
|
||||||
if (globalContext.has('editor')) {
|
if (globalContext.has('editor')) {
|
||||||
globalContext.get('editor').emit('node.add', { node });
|
globalContext.get('editor').emit('node.add', { node });
|
||||||
@ -352,7 +367,7 @@ export class NodeChildren {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(fn: () => void) {
|
onChange(fn: (info?: IOnChangeOptions) => void): () => void {
|
||||||
this.emitter.on('change', fn);
|
this.emitter.on('change', fn);
|
||||||
return () => {
|
return () => {
|
||||||
this.emitter.removeListener('change', fn);
|
this.emitter.removeListener('change', fn);
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
ComponentSchema,
|
ComponentSchema,
|
||||||
NodeStatus,
|
NodeStatus,
|
||||||
CompositeValue,
|
CompositeValue,
|
||||||
|
GlobalEvent,
|
||||||
} from '@ali/lowcode-types';
|
} from '@ali/lowcode-types';
|
||||||
import { compatStage } from '@ali/lowcode-utils';
|
import { compatStage } from '@ali/lowcode-utils';
|
||||||
import { SettingTopEntry } from '@ali/lowcode-designer';
|
import { SettingTopEntry } from '@ali/lowcode-designer';
|
||||||
@ -558,7 +559,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
|||||||
return !this.getExtraProp('hidden')?.getValue();
|
return !this.getExtraProp('hidden')?.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChange(func: (flag: boolean) => any) {
|
onVisibleChange(func: (flag: boolean) => any): () => void {
|
||||||
this.emitter.on('visibleChange', func);
|
this.emitter.on('visibleChange', func);
|
||||||
return () => {
|
return () => {
|
||||||
this.emitter.removeListener('visibleChange', func);
|
this.emitter.removeListener('visibleChange', func);
|
||||||
@ -918,7 +919,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
|||||||
return this.props;
|
return this.props;
|
||||||
}
|
}
|
||||||
|
|
||||||
onChildrenChange(fn: () => void) {
|
onChildrenChange(fn: (param?: { type: string, node: Node }) => void): (() => void) | undefined {
|
||||||
return this.children?.onChange(fn);
|
return this.children?.onChange(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1111,6 +1112,17 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
|||||||
toString() {
|
toString() {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emitPropChange(val: PropChangeOptions) {
|
||||||
|
this.emitter?.emit('propChange', val);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPropChange(func: (info: PropChangeOptions) => void): Function {
|
||||||
|
this.emitter.on('propChange', func);
|
||||||
|
return () => {
|
||||||
|
this.emitter.removeListener('propChange', func);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureNode(node: any, document: DocumentModel): Node {
|
function ensureNode(node: any, document: DocumentModel): Node {
|
||||||
@ -1134,6 +1146,8 @@ export interface LeafNode extends Node {
|
|||||||
readonly children: null;
|
readonly children: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PropChangeOptions = Omit<GlobalEvent.Node.Prop.ChangeOptions, 'node'>;
|
||||||
|
|
||||||
export type SlotNode = ParentalNode<SlotSchema>;
|
export type SlotNode = ParentalNode<SlotSchema>;
|
||||||
export type PageNode = ParentalNode<PageSchema>;
|
export type PageNode = ParentalNode<PageSchema>;
|
||||||
export type ComponentNode = ParentalNode<ComponentSchema>;
|
export type ComponentNode = ParentalNode<ComponentSchema>;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { untracked, computed, obx, engineConfig, action, makeObservable, mobx } from '@ali/lowcode-editor-core';
|
import { untracked, computed, obx, engineConfig, action, makeObservable, mobx } from '@ali/lowcode-editor-core';
|
||||||
import { CompositeValue, FieldConfig, isJSExpression, isJSSlot, JSSlot, SlotSchema } from '@ali/lowcode-types';
|
import { CompositeValue, GlobalEvent, isJSExpression, isJSSlot, JSSlot, SlotSchema } from '@ali/lowcode-types';
|
||||||
import { uniqueId, isPlainObject, hasOwnProperty, compatStage } from '@ali/lowcode-utils';
|
import { uniqueId, isPlainObject, hasOwnProperty, compatStage } from '@ali/lowcode-utils';
|
||||||
import { valueToSource } from './value-to-source';
|
import { valueToSource } from './value-to-source';
|
||||||
import { Props } from './props';
|
import { Props } from './props';
|
||||||
@ -262,12 +262,19 @@ export class Prop implements IPropParent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (oldValue !== this._value) {
|
if (oldValue !== this._value) {
|
||||||
editor?.emit('node.innerProp.change', {
|
const propsInfo = {
|
||||||
node: this.owner,
|
key: this.key,
|
||||||
prop: this,
|
prop: this,
|
||||||
oldValue,
|
oldValue,
|
||||||
newValue: this._value,
|
newValue: this._value,
|
||||||
|
};
|
||||||
|
|
||||||
|
editor?.emit(GlobalEvent.Node.Prop.InnerChange, {
|
||||||
|
node: this.owner as any,
|
||||||
|
...propsInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.owner?.emitPropChange?.(propsInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -152,21 +152,9 @@ export interface ISimulatorHost<P = object> extends ISensor {
|
|||||||
|
|
||||||
getDropContainer(e: LocateEvent): DropContainer | null;
|
getDropContainer(e: LocateEvent): DropContainer | null;
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭 activity 事件
|
|
||||||
*/
|
|
||||||
muteActivityEvent(): void;
|
|
||||||
/**
|
|
||||||
* 打开 activity 事件
|
|
||||||
*/
|
|
||||||
unmuteActivityEvent(): void;
|
|
||||||
/**
|
|
||||||
* 不触发 activity 事件的条件下运行指定 action
|
|
||||||
* @param action
|
|
||||||
*/
|
|
||||||
runWithoutActivity(action: () => void): void;
|
|
||||||
|
|
||||||
postEvent(evtName: string, evtData: any): void;
|
postEvent(evtName: string, evtData: any): void;
|
||||||
|
|
||||||
|
rerender(): void;
|
||||||
/**
|
/**
|
||||||
* 销毁
|
* 销毁
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { StrictEventEmitter } from 'strict-event-emitter-types';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import {
|
import {
|
||||||
IEditor,
|
IEditor,
|
||||||
@ -8,6 +9,7 @@ import {
|
|||||||
HookConfig,
|
HookConfig,
|
||||||
ComponentDescription,
|
ComponentDescription,
|
||||||
RemoteComponentDescription,
|
RemoteComponentDescription,
|
||||||
|
GlobalEvent,
|
||||||
} from '@ali/lowcode-types';
|
} from '@ali/lowcode-types';
|
||||||
import { globalLocale } from './intl';
|
import { globalLocale } from './intl';
|
||||||
import * as utils from './utils';
|
import * as utils from './utils';
|
||||||
@ -16,9 +18,8 @@ import { AssetsJson, AssetLoader } from '@ali/lowcode-utils';
|
|||||||
|
|
||||||
EventEmitter.defaultMaxListeners = 100;
|
EventEmitter.defaultMaxListeners = 100;
|
||||||
|
|
||||||
export declare interface Editor {
|
export declare interface Editor extends StrictEventEmitter<EventEmitter, GlobalEvent.EventConfig> {
|
||||||
addListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
addListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
on(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
||||||
once(event: string | symbol, listener: (...args: any[]) => void): this;
|
once(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
removeListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
removeListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
off(event: string | symbol, listener: (...args: any[]) => void): this;
|
off(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
@ -27,7 +28,6 @@ export declare interface Editor {
|
|||||||
getMaxListeners(): number;
|
getMaxListeners(): number;
|
||||||
listeners(event: string | symbol): Function[];
|
listeners(event: string | symbol): Function[];
|
||||||
rawListeners(event: string | symbol): Function[];
|
rawListeners(event: string | symbol): Function[];
|
||||||
emit(event: string | symbol, ...args: any[]): boolean;
|
|
||||||
listenerCount(type: string | symbol): number;
|
listenerCount(type: string | symbol): number;
|
||||||
// Added in Node 6...
|
// Added in Node 6...
|
||||||
prependListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
prependListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
@ -35,7 +35,7 @@ export declare interface Editor {
|
|||||||
eventNames(): Array<string | symbol>;
|
eventNames(): Array<string | symbol>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Editor extends EventEmitter implements IEditor {
|
export class Editor extends (EventEmitter as any) implements IEditor {
|
||||||
/**
|
/**
|
||||||
* Ioc Container
|
* Ioc Container
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { useRouter } from './rax-use-router';
|
|||||||
import { DocumentInstance, SimulatorRendererContainer } from './renderer';
|
import { DocumentInstance, SimulatorRendererContainer } from './renderer';
|
||||||
import './renderer.less';
|
import './renderer.less';
|
||||||
import { uniqueId } from '@ali/lowcode-utils';
|
import { uniqueId } from '@ali/lowcode-utils';
|
||||||
|
import { GlobalEvent } from '@ali/lowcode-types';
|
||||||
|
import { host } from './host';
|
||||||
|
|
||||||
// patch cloneElement avoid lost keyProps
|
// patch cloneElement avoid lost keyProps
|
||||||
const originCloneElement = (window as any).Rax.cloneElement;
|
const originCloneElement = (window as any).Rax.cloneElement;
|
||||||
@ -145,6 +147,7 @@ class Renderer extends Component<{
|
|||||||
}> {
|
}> {
|
||||||
private unlisten: any;
|
private unlisten: any;
|
||||||
private key: string;
|
private key: string;
|
||||||
|
private startTime: number | null = null;
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.key = uniqueId('renderer');
|
this.key = uniqueId('renderer');
|
||||||
@ -169,12 +172,28 @@ class Renderer extends Component<{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
if (this.startTime) {
|
||||||
|
const time = Date.now() - this.startTime;
|
||||||
|
const nodeCount = host.designer.currentDocument?.getNodeCount?.();
|
||||||
|
host.designer.editor?.emit(GlobalEvent.Node.Rerender, {
|
||||||
|
componentName: 'Renderer',
|
||||||
|
type: 'All',
|
||||||
|
time,
|
||||||
|
nodeCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { documentInstance } = this.props;
|
const { documentInstance } = this.props;
|
||||||
const { container } = documentInstance;
|
const { container } = documentInstance;
|
||||||
const { designMode, device } = container;
|
const { designMode, device } = container;
|
||||||
const { rendererContainer: renderer } = this.props;
|
const { rendererContainer: renderer } = this.props;
|
||||||
|
this.startTime = Date.now();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// @ts-ignore
|
||||||
<RaxRenderer
|
<RaxRenderer
|
||||||
schema={documentInstance.schema}
|
schema={documentInstance.schema}
|
||||||
components={renderer.components}
|
components={renderer.components}
|
||||||
@ -183,6 +202,8 @@ class Renderer extends Component<{
|
|||||||
device={device}
|
device={device}
|
||||||
designMode={renderer.designMode}
|
designMode={renderer.designMode}
|
||||||
key={this.key}
|
key={this.key}
|
||||||
|
__host={host}
|
||||||
|
__container={container}
|
||||||
suspended={documentInstance.suspended}
|
suspended={documentInstance.suspended}
|
||||||
self={documentInstance.scope}
|
self={documentInstance.scope}
|
||||||
onCompGetRef={(schema: any, ref: any) => {
|
onCompGetRef={(schema: any, ref: any) => {
|
||||||
|
|||||||
@ -103,20 +103,12 @@ export class DocumentInstance {
|
|||||||
|
|
||||||
private emitter = new EventEmitter();
|
private emitter = new EventEmitter();
|
||||||
|
|
||||||
@obx.ref private _schema?: RootSchema;
|
get schema(): any {
|
||||||
@computed get schema(): any {
|
return this.document.export(TransformStage.Render);
|
||||||
return this._schema;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private dispose?: () => void;
|
|
||||||
|
|
||||||
constructor(readonly container: SimulatorRendererContainer, readonly document: DocumentModel) {
|
constructor(readonly container: SimulatorRendererContainer, readonly document: DocumentModel) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
this.dispose = host.autorun(() => {
|
|
||||||
// sync schema
|
|
||||||
this._schema = document.export(TransformStage.Render);
|
|
||||||
this.emitter.emit('rerender');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get suspended(): any {
|
@computed get suspended(): any {
|
||||||
@ -498,6 +490,10 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rerender() {
|
||||||
|
this.currentDocumentInstance?.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
createComponent(schema: NodeSchema): Component | null {
|
createComponent(schema: NodeSchema): Component | null {
|
||||||
const _schema: any = {
|
const _schema: any = {
|
||||||
...compatibleLegaoSchema(schema),
|
...compatibleLegaoSchema(schema),
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
# Ali Low-Code React Renderer
|
# Ali Low-Code React Renderer
|
||||||
|
|
||||||
> 低代码引擎渲染模块。
|
> 低代码引擎渲染模块。
|
||||||
|
|
||||||
|
|
||||||
|
👉 [使用文档](https://yuque.antfin-inc.com/docs/share/755c6899-4739-46a1-8d7e-3f5a94d910b8?# 《渲染模块》)
|
||||||
|
|
||||||
|
|||||||
@ -4,13 +4,13 @@ import cn from 'classnames';
|
|||||||
import { Node } from '@ali/lowcode-designer';
|
import { Node } from '@ali/lowcode-designer';
|
||||||
import LowCodeRenderer from '@ali/lowcode-react-renderer';
|
import LowCodeRenderer from '@ali/lowcode-react-renderer';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { isFromVC, getClosestNode } from '@ali/lowcode-utils';
|
import { getClosestNode } from '@ali/lowcode-utils';
|
||||||
|
import { GlobalEvent } from '@ali/lowcode-types';
|
||||||
import { SimulatorRendererContainer, DocumentInstance } from './renderer';
|
import { SimulatorRendererContainer, DocumentInstance } from './renderer';
|
||||||
|
import { host } from './host';
|
||||||
|
|
||||||
import './renderer.less';
|
import './renderer.less';
|
||||||
|
|
||||||
const DEFAULT_SIMULATOR_LOCALE = 'zh-CN';
|
|
||||||
|
|
||||||
// patch cloneElement avoid lost keyProps
|
// patch cloneElement avoid lost keyProps
|
||||||
const originCloneElement = window.React.cloneElement;
|
const originCloneElement = window.React.cloneElement;
|
||||||
(window as any).React.cloneElement = (child: any, { _leaf, ...props }: any = {}, ...rest: any[]) => {
|
(window as any).React.cloneElement = (child: any, { _leaf, ...props }: any = {}, ...rest: any[]) => {
|
||||||
@ -129,11 +129,27 @@ class Renderer extends Component<{
|
|||||||
rendererContainer: SimulatorRendererContainer,
|
rendererContainer: SimulatorRendererContainer,
|
||||||
documentInstance: DocumentInstance,
|
documentInstance: DocumentInstance,
|
||||||
}> {
|
}> {
|
||||||
|
startTime: number | null = null;
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
if (this.startTime) {
|
||||||
|
const time = Date.now() - this.startTime;
|
||||||
|
const nodeCount = host.designer.currentDocument?.getNodeCount?.();
|
||||||
|
host.designer.editor?.emit(GlobalEvent.Node.Rerender, {
|
||||||
|
componentName: 'Renderer',
|
||||||
|
type: 'All',
|
||||||
|
time,
|
||||||
|
nodeCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { documentInstance, rendererContainer: renderer } = this.props;
|
const { documentInstance, rendererContainer: renderer } = this.props;
|
||||||
const { container } = documentInstance;
|
const { container } = documentInstance;
|
||||||
const { designMode, device, locale } = container;
|
const { designMode, device, locale } = container;
|
||||||
const messages = container.context?.utils?.i18n?.messages || {};
|
const messages = container.context?.utils?.i18n?.messages || {};
|
||||||
|
this.startTime = Date.now();
|
||||||
|
|
||||||
if (!container.autoRender) return null;
|
if (!container.autoRender) return null;
|
||||||
return (
|
return (
|
||||||
@ -154,9 +170,7 @@ class Renderer extends Component<{
|
|||||||
const { __id, __designMode, ...viewProps } = props;
|
const { __id, __designMode, ...viewProps } = props;
|
||||||
viewProps.componentId = __id;
|
viewProps.componentId = __id;
|
||||||
const leaf = documentInstance.getNode(__id) as Node;
|
const leaf = documentInstance.getNode(__id) as Node;
|
||||||
if (isFromVC(leaf?.componentMeta)) {
|
viewProps._leaf = leaf;
|
||||||
viewProps._leaf = leaf;
|
|
||||||
}
|
|
||||||
viewProps._componentName = leaf?.componentName;
|
viewProps._componentName = leaf?.componentName;
|
||||||
// 如果是容器 && 无children && 高宽为空 增加一个占位容器,方便拖动
|
// 如果是容器 && 无children && 高宽为空 增加一个占位容器,方便拖动
|
||||||
if (
|
if (
|
||||||
@ -207,12 +221,11 @@ class Renderer extends Component<{
|
|||||||
leaf?.isContainer() ? (children == null ? [] : Array.isArray(children) ? children : [children]) : children,
|
leaf?.isContainer() ? (children == null ? [] : Array.isArray(children) ? children : [children]) : children,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
__host={host}
|
||||||
|
__container={container}
|
||||||
onCompGetRef={(schema: any, ref: ReactInstance | null) => {
|
onCompGetRef={(schema: any, ref: ReactInstance | null) => {
|
||||||
documentInstance.mountInstance(schema.id, ref);
|
documentInstance.mountInstance(schema.id, ref);
|
||||||
}}
|
}}
|
||||||
// onCompGetCtx={(schema: any, ctx: object) => {
|
|
||||||
// documentInstance.mountContext(schema.id, ctx);
|
|
||||||
// }}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import { createMemoryHistory, MemoryHistory } from 'history';
|
|||||||
import Slot from './builtin-components/slot';
|
import Slot from './builtin-components/slot';
|
||||||
import Leaf from './builtin-components/leaf';
|
import Leaf from './builtin-components/leaf';
|
||||||
import { withQueryParams, parseQuery } from './utils/url';
|
import { withQueryParams, parseQuery } from './utils/url';
|
||||||
import { supportsQuickPropSetting, getUppermostPropKey, setInstancesProp } from './utils/misc';
|
|
||||||
const loader = new AssetLoader();
|
const loader = new AssetLoader();
|
||||||
const DELAY_THRESHOLD = 10;
|
const DELAY_THRESHOLD = 10;
|
||||||
const FULL_RENDER_THRESHOLD = 500;
|
const FULL_RENDER_THRESHOLD = 500;
|
||||||
@ -35,60 +35,14 @@ configure({ enforceActions: 'never' });
|
|||||||
export class DocumentInstance {
|
export class DocumentInstance {
|
||||||
public instancesMap = new Map<string, ReactInstance[]>();
|
public instancesMap = new Map<string, ReactInstance[]>();
|
||||||
|
|
||||||
@obx.ref private _schema?: RootSchema;
|
get schema(): any {
|
||||||
@computed get schema(): any {
|
return this.document.export(TransformStage.Render);
|
||||||
return this._schema;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private disposeFunctions: Array<() => void> = [];
|
private disposeFunctions: Array<() => void> = [];
|
||||||
|
|
||||||
constructor(readonly container: SimulatorRendererContainer, readonly document: DocumentModel) {
|
constructor(readonly container: SimulatorRendererContainer, readonly document: DocumentModel) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
// 标识当前文档导出的状态,用来控制 reaction effect 是否执行
|
|
||||||
let asleep = false;
|
|
||||||
const setDocSchema = (value?: any) => {
|
|
||||||
this._schema = value || document.export(TransformStage.Render);
|
|
||||||
};
|
|
||||||
const documentExportDisposer = host.reaction(() => {
|
|
||||||
return document.export(TransformStage.Render);
|
|
||||||
}, (value) => {
|
|
||||||
if (asleep) return;
|
|
||||||
setDocSchema(value);
|
|
||||||
}, { delay: DELAY_THRESHOLD, fireImmediately: true });
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 当节点数小数 200 个时,不走入增量更新逻辑,后面优化、细化逻辑后逐步放开限制
|
|
||||||
if (this.document.nodesMap.size < 200) return;
|
|
||||||
|
|
||||||
if (tid) clearTimeout(tid);
|
|
||||||
// 临时关闭全量计算 schema 的逻辑,在增量计算结束后,来一次全量计算
|
|
||||||
asleep = true;
|
|
||||||
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(() => {
|
|
||||||
asleep = false;
|
|
||||||
setDocSchema();
|
|
||||||
}, FULL_RENDER_THRESHOLD);
|
|
||||||
// TODO: 调试增量模式,打开以下代码
|
|
||||||
// this._deltaData = data;
|
|
||||||
// this._deltaMode = true;
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@obx.ref private _components: any = {};
|
@obx.ref private _components: any = {};
|
||||||
|
|||||||
@ -1,8 +1,3 @@
|
|||||||
import { ReactInstance } from 'react';
|
|
||||||
import { ActivityData, isJSSlot } from '@ali/lowcode-types';
|
|
||||||
import { DocumentInstance } from '../renderer';
|
|
||||||
import { isReactClass } from '@ali/lowcode-utils';
|
|
||||||
|
|
||||||
interface UtilsMetadata {
|
interface UtilsMetadata {
|
||||||
name: string;
|
name: string;
|
||||||
npm: {
|
npm: {
|
||||||
@ -29,92 +24,3 @@ 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.isProp) {
|
|
||||||
curProp = curProp.parent;
|
|
||||||
}
|
|
||||||
return curProp.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
function haveForceUpdate(instances: any[]): boolean {
|
|
||||||
return instances.every(inst => 'forceUpdate' in inst);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPropWritable(props: any, propName: string): boolean {
|
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(props, propName);
|
|
||||||
return !!descriptor?.writable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否支持快捷属性值设值
|
|
||||||
* @param data
|
|
||||||
* @param doc
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function supportsQuickPropSetting(data: ActivityData, doc: DocumentInstance) {
|
|
||||||
const { payload } = data;
|
|
||||||
const { schema, prop } = payload;
|
|
||||||
const { componentName, id: nodeId } = schema;
|
|
||||||
// const key = data.payload.prop.key;
|
|
||||||
const instances = doc.instancesMap.get(nodeId!);
|
|
||||||
const propKey = getUppermostPropKey(prop);
|
|
||||||
let value = (schema.props as any)[propKey];
|
|
||||||
|
|
||||||
return (
|
|
||||||
nodeId &&
|
|
||||||
Array.isArray(instances) &&
|
|
||||||
instances.length > 0 &&
|
|
||||||
propKey &&
|
|
||||||
// 不是 extraProp
|
|
||||||
!propKey.startsWith('___') &&
|
|
||||||
// props[propKey] 有可能是 readyonly 的
|
|
||||||
instances.every(inst => isPropWritable(inst.props, propKey)) &&
|
|
||||||
!isJSSlot(value) &&
|
|
||||||
// functional component 不支持 ref.props 直接设值,是 readonly 的
|
|
||||||
isReactClass((doc.container?.components as any)[componentName]) &&
|
|
||||||
haveForceUpdate(instances) &&
|
|
||||||
// 黑名单组件通常会把 schema / children 魔改,导致直接设置 props 失效
|
|
||||||
isAllowedComponent(componentName)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 不允许走快捷设置的组件黑名单
|
|
||||||
const DISABLED__QUICK_SETTING_COMPONENT_NAMES = [
|
|
||||||
'Filter', // 查询组件
|
|
||||||
'Filter2', // 查询组件
|
|
||||||
'TableField', // 明细组件
|
|
||||||
];
|
|
||||||
function isAllowedComponent(name: string): boolean {
|
|
||||||
return !DISABLED__QUICK_SETTING_COMPONENT_NAMES.includes(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置属性值
|
|
||||||
* @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.isPropStash) {
|
|
||||||
value = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
instances.forEach((inst: any) => {
|
|
||||||
inst.props[propKey] = value;
|
|
||||||
inst.forceUpdate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -17,6 +17,7 @@
|
|||||||
"@ali/bzb-request": "^2.6.0-beta.13",
|
"@ali/bzb-request": "^2.6.0-beta.13",
|
||||||
"@ali/lib-mtop": "^2.5.1",
|
"@ali/lib-mtop": "^2.5.1",
|
||||||
"@ali/lowcode-datasource-engine": "^1.0.22",
|
"@ali/lowcode-datasource-engine": "^1.0.22",
|
||||||
|
"@ali/lowcode-types": "1.0.67",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"fetch-jsonp": "^1.1.3",
|
"fetch-jsonp": "^1.1.3",
|
||||||
|
|||||||
300
packages/renderer-core/src/hoc/leaf.tsx
Normal file
300
packages/renderer-core/src/hoc/leaf.tsx
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
import { BuiltinSimulatorHost, Node, PropChangeOptions } from '@ali/lowcode-designer';
|
||||||
|
import { GlobalEvent, TransformStage } from '@ali/lowcode-types';
|
||||||
|
import { EngineOptions } from '@ali/lowcode-editor-core';
|
||||||
|
import adapter from '../adapter';
|
||||||
|
import * as types from '../types/index';
|
||||||
|
|
||||||
|
const compDefaultPropertyNames = ['$$typeof', 'render', 'defaultProps'];
|
||||||
|
|
||||||
|
export interface IComponentHocInfo {
|
||||||
|
schema: any;
|
||||||
|
baseRenderer: types.IBaseRendererInstance;
|
||||||
|
componentInfo: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
type DesignMode = Pick<EngineOptions, 'designMode'>['designMode'];
|
||||||
|
|
||||||
|
export type IComponentHoc = {
|
||||||
|
designMode: DesignMode | DesignMode[];
|
||||||
|
hoc: IComponentConstruct,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IComponentConstruct = (Comp: types.IBaseRenderer, info: IComponentHocInfo) => types.Constructor;
|
||||||
|
|
||||||
|
const whitelist: string[] = [];
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
_leaf: Node | undefined;
|
||||||
|
|
||||||
|
visible: boolean;
|
||||||
|
|
||||||
|
componentId?: number;
|
||||||
|
|
||||||
|
children?: Node[];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RerenderType {
|
||||||
|
All = 'All',
|
||||||
|
ChildChanged = 'ChildChanged',
|
||||||
|
PropsChanged = 'PropsChanged',
|
||||||
|
VisibleChanged = 'VisibleChanged',
|
||||||
|
LangChanged = 'LangChanged',
|
||||||
|
I18nChanged = 'I18nChanged',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 给每个组件包裹一个 HOC Leaf,支持组件内部属性变化,自响应渲染
|
||||||
|
export function leafWrapper(Comp: types.IBaseRenderer, {
|
||||||
|
schema,
|
||||||
|
baseRenderer,
|
||||||
|
componentInfo,
|
||||||
|
}: IComponentHocInfo) {
|
||||||
|
const {
|
||||||
|
__debug,
|
||||||
|
__getComponentProps: getProps,
|
||||||
|
__getSchemaChildrenVirtualDom: getChildren,
|
||||||
|
} = baseRenderer;
|
||||||
|
const engine = baseRenderer.context.engine;
|
||||||
|
const host: BuiltinSimulatorHost = baseRenderer.props.__host;
|
||||||
|
const container: BuiltinSimulatorHost = baseRenderer.props.__container;
|
||||||
|
const editor = host?.designer?.editor;
|
||||||
|
const { Component } = adapter.getRuntime();
|
||||||
|
|
||||||
|
class LeafWrapper extends Component {
|
||||||
|
recordInfo: {
|
||||||
|
startTime?: number | null;
|
||||||
|
type?: string;
|
||||||
|
node?: Node;
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
static displayName = schema.componentName;
|
||||||
|
|
||||||
|
disposeFunctions: ((() => void) | Function)[] = [];
|
||||||
|
|
||||||
|
recordTime = () => {
|
||||||
|
if (!this.recordInfo.startTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const endTime = Date.now();
|
||||||
|
const nodeCount = host.designer.currentDocument?.getNodeCount?.();
|
||||||
|
const componentName = this.recordInfo.node?.componentName || this.leaf?.componentName || 'UnknownComponent';
|
||||||
|
editor?.emit(GlobalEvent.Node.Rerender, {
|
||||||
|
componentName,
|
||||||
|
time: endTime - this.recordInfo.startTime,
|
||||||
|
type: this.recordInfo.type,
|
||||||
|
nodeCount,
|
||||||
|
});
|
||||||
|
this.recordInfo.startTime = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.recordTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props: IProps, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
// 监听以下事件,当变化时更新自己
|
||||||
|
this.initOnPropsChangeEvent();
|
||||||
|
this.initOnChildrenChangeEvent();
|
||||||
|
this.initOnVisibleChangeEvent();
|
||||||
|
this.initLangChangeEvent();
|
||||||
|
this.state = {
|
||||||
|
nodeChildren: null,
|
||||||
|
childrenInState: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 由于内部属性变化,在触发渲染前,会执行该函数 */
|
||||||
|
beforeRender(type: string, node?: Node): void {
|
||||||
|
this.recordInfo.startTime = Date.now();
|
||||||
|
this.recordInfo.type = type;
|
||||||
|
this.recordInfo.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate() {
|
||||||
|
if (whitelist.includes(schema.componentName)) {
|
||||||
|
__debug(`${schema.componentName} is in leaf Hoc whitelist`);
|
||||||
|
container.rerender();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 监听参数变化 */
|
||||||
|
initOnPropsChangeEvent(): void {
|
||||||
|
const dispose = this.leaf?.onPropChange?.((propChangeInfo: PropChangeOptions) => {
|
||||||
|
const {
|
||||||
|
key,
|
||||||
|
} = propChangeInfo;
|
||||||
|
const node = this.leaf;
|
||||||
|
|
||||||
|
// 如果循坏条件变化,从根节点重新渲染
|
||||||
|
// 目前多层循坏无法判断需要从哪一层开始渲染,故先粗暴解决
|
||||||
|
if (key === '___loop___') {
|
||||||
|
container.rerender();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.beforeRender(RerenderType.PropsChanged);
|
||||||
|
__debug(`${this.leaf?.componentName} component trigger onPropsChange event`);
|
||||||
|
const nextProps = getProps(node?.export?.(TransformStage.Render) as types.ISchema, Comp, componentInfo);
|
||||||
|
this.setState(nextProps.children ? {
|
||||||
|
nodeChildren: nextProps.children,
|
||||||
|
nodeProps: nextProps,
|
||||||
|
} : {
|
||||||
|
nodeProps: nextProps,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dispose && this.disposeFunctions.push(dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听显隐变化
|
||||||
|
*/
|
||||||
|
initOnVisibleChangeEvent() {
|
||||||
|
const dispose = this.leaf?.onVisibleChange?.((flag: boolean) => {
|
||||||
|
if (this.state.visible === flag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
__debug(`${this.leaf?.componentName} component trigger onVisibleChange event`);
|
||||||
|
this.beforeRender(RerenderType.VisibleChanged);
|
||||||
|
this.setState({
|
||||||
|
visible: flag,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dispose && this.disposeFunctions.push(dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听子元素变化(拖拽,删除...)
|
||||||
|
*/
|
||||||
|
initOnChildrenChangeEvent() {
|
||||||
|
const dispose = this.leaf?.onChildrenChange?.((param): void => {
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
node,
|
||||||
|
} = param || {};
|
||||||
|
this.beforeRender(`${RerenderType.ChildChanged}-${type}`, node);
|
||||||
|
__debug(`${this.leaf} component trigger onChildrenChange event`);
|
||||||
|
const nextChild = getChildren(this.leaf?.export?.(TransformStage.Render) as types.ISchema, Comp);
|
||||||
|
this.setState({
|
||||||
|
nodeChildren: nextChild,
|
||||||
|
childrenInState: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dispose && this.disposeFunctions.push(dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听语言切换
|
||||||
|
*/
|
||||||
|
initLangChangeEvent() {
|
||||||
|
if (this.leaf?.componentName !== 'Page') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dispose = (window as any).VisualEngine.Env.onEnvChange(() => {
|
||||||
|
this.beforeRender(RerenderType.LangChanged);
|
||||||
|
const nextProps = getProps(this.leaf?.export?.(TransformStage.Render) as types.ISchema, Comp, this.componentInfo);
|
||||||
|
const nextChildren = getChildren(this.leaf?.export?.(TransformStage.Render) as types.ISchema, Comp);
|
||||||
|
this.setState({
|
||||||
|
nodeChildren: nextChildren,
|
||||||
|
nodeProps: nextProps,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dispose && this.disposeFunctions.push(dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.disposeFunctions.forEach(fn => fn());
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasChildren(): boolean {
|
||||||
|
if (this.state.childrenInState) {
|
||||||
|
return !!this.state.nodeChildren?.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(this.props.children)) {
|
||||||
|
return Boolean(this.props.children && this.props.children.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Boolean(this.props.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
get children(): any {
|
||||||
|
if (this.state.nodeChildren) {
|
||||||
|
return this.state.nodeChildren;
|
||||||
|
}
|
||||||
|
if (this.props.children && !Array.isArray(this.props.children)) {
|
||||||
|
return [this.props.children];
|
||||||
|
}
|
||||||
|
if (this.props.children && this.props.children.length) {
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get leaf(): Node | undefined {
|
||||||
|
return this.props._leaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
get childrenMap(): any {
|
||||||
|
const map = new Map();
|
||||||
|
|
||||||
|
if (!this.hasChildren) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.children.forEach((d: any) => {
|
||||||
|
if (Array.isArray(d)) {
|
||||||
|
map.set(d[0].props.componentId, d[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
map.set(d.props.componentId, d);
|
||||||
|
});
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
get visible(): boolean {
|
||||||
|
if (typeof this.state.visible === 'boolean') {
|
||||||
|
return this.state.visible;
|
||||||
|
}
|
||||||
|
if (typeof this.leaf?.schema?.hidden === 'boolean') {
|
||||||
|
return !this.leaf?.schema?.hidden;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const compProps = {
|
||||||
|
...this.props,
|
||||||
|
...(this.state.nodeProps || {}),
|
||||||
|
children: [],
|
||||||
|
__id: this.props.componentId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return engine.createElement(Comp, compProps, this.hasChildren ? this.children : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof Comp === 'object') {
|
||||||
|
const compExtraPropertyNames = Object.getOwnPropertyNames(Comp).filter(d => !compDefaultPropertyNames.includes(d));
|
||||||
|
|
||||||
|
compExtraPropertyNames.forEach((d: string) => {
|
||||||
|
(LeafWrapper as any)[d] = Comp[d];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
LeafWrapper.displayName = (Comp as any).displayName;
|
||||||
|
|
||||||
|
return LeafWrapper;
|
||||||
|
}
|
||||||
@ -30,6 +30,7 @@ import {
|
|||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { IRendererProps, ISchema, IInfo, ComponentModel, IRenderer } from '../types';
|
import { IRendererProps, ISchema, IInfo, ComponentModel, IRenderer } from '../types';
|
||||||
import { compWrapper } from '../hoc';
|
import { compWrapper } from '../hoc';
|
||||||
|
import { IComponentConstruct, IComponentHoc, leafWrapper } from '../hoc/leaf';
|
||||||
|
|
||||||
export default function baseRenererFactory() {
|
export default function baseRenererFactory() {
|
||||||
const { BaseRenderer: customBaseRenderer } = adapter.getRenderers();
|
const { BaseRenderer: customBaseRenderer } = adapter.getRenderers();
|
||||||
@ -53,7 +54,7 @@ export default function baseRenererFactory() {
|
|||||||
let scopeIdx = 0;
|
let scopeIdx = 0;
|
||||||
|
|
||||||
return class BaseRenderer extends Component implements IRenderer {
|
return class BaseRenderer extends Component implements IRenderer {
|
||||||
static dislayName = 'base-renderer';
|
static displayName = 'base-renderer';
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
__schema: {},
|
__schema: {},
|
||||||
@ -61,8 +62,12 @@ export default function baseRenererFactory() {
|
|||||||
|
|
||||||
static contextType = AppContext;
|
static contextType = AppContext;
|
||||||
|
|
||||||
|
__hoc_components: any = {};
|
||||||
|
|
||||||
__namespace = 'base';
|
__namespace = 'base';
|
||||||
|
|
||||||
|
_self: any = null;
|
||||||
|
|
||||||
constructor(props: IRendererProps, context: any) {
|
constructor(props: IRendererProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.__beforeInit(props);
|
this.__beforeInit(props);
|
||||||
@ -332,10 +337,21 @@ export default function baseRenererFactory() {
|
|||||||
const { __schema, __ctx, __components = {} } = this.props;
|
const { __schema, __ctx, __components = {} } = this.props;
|
||||||
const self: any = {};
|
const self: any = {};
|
||||||
self.__proto__ = __ctx || this;
|
self.__proto__ = __ctx || this;
|
||||||
|
if (!this._self) {
|
||||||
|
this._self = self;
|
||||||
|
}
|
||||||
const _children = this.getSchemaChildren(__schema);
|
const _children = this.getSchemaChildren(__schema);
|
||||||
|
let Comp = __components[__schema.componentName];
|
||||||
|
this.componentHoc.forEach((ComponentConstruct: IComponentConstruct) => {
|
||||||
|
Comp = ComponentConstruct(Comp || Div, {
|
||||||
|
schema: __schema,
|
||||||
|
componentInfo: {},
|
||||||
|
baseRenderer: this,
|
||||||
|
});
|
||||||
|
});
|
||||||
return this.__createVirtualDom(_children, self, ({
|
return this.__createVirtualDom(_children, self, ({
|
||||||
schema: __schema,
|
schema: __schema,
|
||||||
Comp: __components[__schema.componentName],
|
Comp,
|
||||||
} as IInfo));
|
} as IInfo));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -352,7 +368,7 @@ export default function baseRenererFactory() {
|
|||||||
// self 为每个渲染组件构造的上下文,self是自上而下继承的
|
// self 为每个渲染组件构造的上下文,self是自上而下继承的
|
||||||
// parentInfo 父组件的信息,包含schema和Comp
|
// parentInfo 父组件的信息,包含schema和Comp
|
||||||
// idx 若为循环渲染的循环Index
|
// idx 若为循环渲染的循环Index
|
||||||
__createVirtualDom = (schema: any, self: any, parentInfo: IInfo, idx: string | number = ''): any => {
|
__createVirtualDom = (schema: ISchema, self: any, parentInfo: IInfo, idx: string | number = ''): any => {
|
||||||
const { engine } = this.context || {};
|
const { engine } = this.context || {};
|
||||||
try {
|
try {
|
||||||
if (!schema) return null;
|
if (!schema) return null;
|
||||||
@ -376,7 +392,7 @@ export default function baseRenererFactory() {
|
|||||||
}
|
}
|
||||||
if (typeof schema === 'string') return schema;
|
if (typeof schema === 'string') return schema;
|
||||||
if (typeof schema === 'number' || typeof schema === 'boolean') {
|
if (typeof schema === 'number' || typeof schema === 'boolean') {
|
||||||
return schema.toString();
|
return String(schema);
|
||||||
}
|
}
|
||||||
if (Array.isArray(schema)) {
|
if (Array.isArray(schema)) {
|
||||||
if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo);
|
if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo);
|
||||||
@ -394,9 +410,15 @@ export default function baseRenererFactory() {
|
|||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
if (!isSchema(schema)) return null;
|
if (!isSchema(schema)) return null;
|
||||||
let Comp = components[schema.componentName] || engine.getNotFoundComponent();
|
let Comp = components[schema.componentName] || this.props.__container?.components?.[schema.componentName];
|
||||||
|
|
||||||
if (schema.hidden && engine?.props?.designMode) {
|
if (!Comp) {
|
||||||
|
console.error(`${schema.componentName} is not found! component list is:`, this.props.__container?.components);
|
||||||
|
Comp = engine.getNotFoundComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.hidden && !this._designModeIsDesign) {
|
||||||
|
// designMode 为 design 情况下,需要进入 leaf Hoc,进行相关事件注册
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,21 +479,34 @@ export default function baseRenererFactory() {
|
|||||||
otherProps.__designMode = engine.props.designMode;
|
otherProps.__designMode = engine.props.designMode;
|
||||||
}
|
}
|
||||||
const componentInfo: any = {};
|
const componentInfo: any = {};
|
||||||
const props: any =
|
const props: any = this.__getComponentProps(schema, Comp, {
|
||||||
this.__parseProps(schema.props, self, '', {
|
...componentInfo,
|
||||||
schema,
|
props: transformArrayToMap(componentInfo.props, 'name'),
|
||||||
Comp,
|
}) || {};
|
||||||
componentInfo: {
|
|
||||||
...componentInfo,
|
if (!this.__hoc_components[schema.componentName]) {
|
||||||
props: transformArrayToMap(componentInfo.props, 'name'),
|
this.componentHoc.forEach((ComponentConstruct: IComponentConstruct) => {
|
||||||
},
|
Comp = ComponentConstruct(Comp, {
|
||||||
}) || {};
|
schema,
|
||||||
|
componentInfo,
|
||||||
|
baseRenderer: this,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 对于可以获取到ref的组件做特殊处理
|
// 对于可以获取到ref的组件做特殊处理
|
||||||
if (!acceptsRef(Comp)) {
|
if (!acceptsRef(Comp) && !this.__hoc_components[schema.componentName]) {
|
||||||
Comp = compWrapper(Comp);
|
Comp = compWrapper(Comp);
|
||||||
components[schema.componentName] = Comp;
|
components[schema.componentName] = Comp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.__hoc_components[schema.componentName]) {
|
||||||
|
this.__hoc_components[schema.componentName] = Comp;
|
||||||
|
} else {
|
||||||
|
Comp = this.__hoc_components[schema.componentName];
|
||||||
|
}
|
||||||
|
|
||||||
otherProps.ref = (ref: any) => {
|
otherProps.ref = (ref: any) => {
|
||||||
this.$(props.fieldId || props.ref, ref); // 收集ref
|
this.$(props.fieldId || props.ref, ref); // 收集ref
|
||||||
const refProps = props.ref;
|
const refProps = props.ref;
|
||||||
@ -500,17 +535,7 @@ export default function baseRenererFactory() {
|
|||||||
props.key = props.__id;
|
props.key = props.__id;
|
||||||
}
|
}
|
||||||
|
|
||||||
let child: any = null;
|
let child: any = this.__getSchemaChildrenVirtualDom(schema, Comp);
|
||||||
if (/*! isFileSchema(schema) && */_children) {
|
|
||||||
child = this.__createVirtualDom(
|
|
||||||
isJSExpression(_children) ? parseExpression(_children, self) : _children,
|
|
||||||
self,
|
|
||||||
{
|
|
||||||
schema,
|
|
||||||
Comp,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const renderComp = (props: any) => engine.createElement(Comp, props, child);
|
const renderComp = (props: any) => engine.createElement(Comp, props, child);
|
||||||
// 设计模式下的特殊处理
|
// 设计模式下的特殊处理
|
||||||
if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) {
|
if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) {
|
||||||
@ -550,7 +575,69 @@ export default function baseRenererFactory() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
__createLoopVirtualDom = (schema: any, self: any, parentInfo: IInfo, idx: number | string) => {
|
get componentHoc(): IComponentConstruct[] {
|
||||||
|
const componentHoc: IComponentHoc[] = [
|
||||||
|
{
|
||||||
|
designMode: 'design',
|
||||||
|
hoc: leafWrapper,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return componentHoc
|
||||||
|
.filter((d: IComponentHoc) => {
|
||||||
|
if (Array.isArray(d.designMode)) {
|
||||||
|
return d.designMode.includes(this.props.designMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.designMode === this.props.designMode;
|
||||||
|
})
|
||||||
|
.map((d: IComponentHoc) => d.hoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
__getSchemaChildrenVirtualDom = (schema: ISchema, Comp: any) => {
|
||||||
|
let _children = this.getSchemaChildren(schema);
|
||||||
|
|
||||||
|
let children: any = [];
|
||||||
|
if (/*! isFileSchema(schema) && */_children) {
|
||||||
|
if (!Array.isArray(_children)) {
|
||||||
|
_children = [_children];
|
||||||
|
}
|
||||||
|
|
||||||
|
_children.forEach((_child: any) => {
|
||||||
|
const _childVirtualDom = this.__createVirtualDom(
|
||||||
|
isJSExpression(_child) ? parseExpression(_child, self) : _child,
|
||||||
|
self,
|
||||||
|
{
|
||||||
|
schema,
|
||||||
|
Comp,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
children.push(_childVirtualDom);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children && children.length) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
__getComponentProps = (schema: ISchema, Comp: any, componentInfo?: any) => {
|
||||||
|
if (!schema) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return this.__parseProps(schema?.props, this.self, '', {
|
||||||
|
schema,
|
||||||
|
Comp,
|
||||||
|
componentInfo: {
|
||||||
|
...(componentInfo || {}),
|
||||||
|
props: transformArrayToMap((componentInfo || {}).props, 'name'),
|
||||||
|
},
|
||||||
|
}) || {};
|
||||||
|
};
|
||||||
|
|
||||||
|
__createLoopVirtualDom = (schema: ISchema, self: any, parentInfo: IInfo, idx: number | string) => {
|
||||||
if (isFileSchema(schema)) {
|
if (isFileSchema(schema)) {
|
||||||
console.warn('file type not support Loop');
|
console.warn('file type not support Loop');
|
||||||
return null;
|
return null;
|
||||||
@ -576,6 +663,11 @@ export default function baseRenererFactory() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
get _designModeIsDesign() {
|
||||||
|
const { engine } = this.context || {};
|
||||||
|
return engine?.props?.designMode === 'design';
|
||||||
|
}
|
||||||
|
|
||||||
__parseProps = (props: any, self: any, path: string, info: IInfo): any => {
|
__parseProps = (props: any, self: any, path: string, info: IInfo): any => {
|
||||||
const { schema, Comp, componentInfo = {} } = info;
|
const { schema, Comp, componentInfo = {} } = info;
|
||||||
const propInfo = getValue(componentInfo.props, path);
|
const propInfo = getValue(componentInfo.props, path);
|
||||||
@ -721,6 +813,7 @@ export default function baseRenererFactory() {
|
|||||||
__renderContextProvider = (customProps?: object, children?: any) => {
|
__renderContextProvider = (customProps?: object, children?: any) => {
|
||||||
customProps = customProps || {};
|
customProps = customProps || {};
|
||||||
children = children || this.__createDom();
|
children = children || this.__createDom();
|
||||||
|
this.__hoc_components = {};
|
||||||
return createElement(AppContext.Provider, {
|
return createElement(AppContext.Provider, {
|
||||||
value: {
|
value: {
|
||||||
...this.context,
|
...this.context,
|
||||||
@ -742,13 +835,21 @@ export default function baseRenererFactory() {
|
|||||||
Comp,
|
Comp,
|
||||||
componentInfo: {},
|
componentInfo: {},
|
||||||
});
|
});
|
||||||
|
this.__hoc_components = {};
|
||||||
const { className } = data;
|
const { className } = data;
|
||||||
const { engine } = this.context || {};
|
const { engine } = this.context || {};
|
||||||
if (!engine) {
|
if (!engine) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
this.componentHoc.forEach((ComponentConstruct: IComponentConstruct) => {
|
||||||
|
Comp = ComponentConstruct(Comp || Div, {
|
||||||
|
schema: __schema,
|
||||||
|
componentInfo: {},
|
||||||
|
baseRenderer: this,
|
||||||
|
});
|
||||||
|
});
|
||||||
const child = engine.createElement(
|
const child = engine.createElement(
|
||||||
Comp || Div,
|
Comp,
|
||||||
{
|
{
|
||||||
...data,
|
...data,
|
||||||
...this.props,
|
...this.props,
|
||||||
|
|||||||
@ -38,7 +38,6 @@ export default function rendererFactory() {
|
|||||||
|
|
||||||
class NotFoundComponent extends PureComponent {
|
class NotFoundComponent extends PureComponent {
|
||||||
render() {
|
render() {
|
||||||
console.error('component not found', this.props);
|
|
||||||
return createElement(Div, this.props, this.props.children || 'Component Not Found');
|
return createElement(Div, this.props, this.props.children || 'Component Not Found');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,10 @@
|
|||||||
|
import { BuiltinSimulatorHost } from '@ali/lowcode-designer';
|
||||||
|
import { baseRendererFactory } from '../renderer';
|
||||||
|
import baseRenererFactory from '../renderer/base';
|
||||||
|
|
||||||
|
export type IBaseRenderer = ReturnType<typeof baseRenererFactory>;
|
||||||
|
export type IBaseRendererInstance = InstanceType<ReturnType<typeof baseRendererFactory>>;
|
||||||
|
|
||||||
export interface IProps {
|
export interface IProps {
|
||||||
schema: ISchema;
|
schema: ISchema;
|
||||||
components: { [key: string]: any };
|
components: { [key: string]: any };
|
||||||
@ -17,15 +24,22 @@ export interface IProps {
|
|||||||
export interface IState {
|
export interface IState {
|
||||||
engineRenderError?: boolean;
|
engineRenderError?: boolean;
|
||||||
error?: Error
|
error?: Error
|
||||||
|
onCompGetRef: (schema: ISchema, ref: any) => void;
|
||||||
|
onCompGetCtx: (schema: ISchema, ref: any) => void;
|
||||||
|
customCreateElement: (...args: any) => any;
|
||||||
|
notFoundComponent: any;
|
||||||
|
faultComponent: any;
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRendererProps {
|
export interface IRendererProps {
|
||||||
locale: string;
|
locale?: string;
|
||||||
messages: object;
|
messages: object;
|
||||||
__appHelper: object;
|
__appHelper: object;
|
||||||
__components: object;
|
__components: object;
|
||||||
__ctx: object;
|
__ctx: object;
|
||||||
__schema: ISchema;
|
__schema: ISchema;
|
||||||
|
__host?: BuiltinSimulatorHost;
|
||||||
|
__container?: any;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +60,7 @@ export interface ISchema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IInfo {
|
export interface IInfo {
|
||||||
schema: any;
|
schema: ISchema;
|
||||||
Comp: any;
|
Comp: any;
|
||||||
componentInfo?: any;
|
componentInfo?: any;
|
||||||
}
|
}
|
||||||
@ -77,7 +91,7 @@ export interface DataSource {
|
|||||||
dataHandler: JSExpression;
|
dataHandler: JSExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Constructor = new(...args: any) => any;
|
export type Constructor = new(...args: any) => any;
|
||||||
|
|
||||||
export interface IRuntime {
|
export interface IRuntime {
|
||||||
Component: Constructor;
|
Component: Constructor;
|
||||||
@ -90,7 +104,7 @@ export interface IRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IRendererModules {
|
export interface IRendererModules {
|
||||||
BaseRenderer?: any;
|
BaseRenderer?: new(...args: any) => IRenderer;
|
||||||
PageRenderer: any;
|
PageRenderer: any;
|
||||||
ComponentRenderer: any;
|
ComponentRenderer: any;
|
||||||
BlockRenderer?: any,
|
BlockRenderer?: any,
|
||||||
@ -102,10 +116,11 @@ export interface IRendererModules {
|
|||||||
export interface IRenderer {
|
export interface IRenderer {
|
||||||
props?: IRendererProps;
|
props?: IRendererProps;
|
||||||
context?: any;
|
context?: any;
|
||||||
|
reloadDataSource: () => Promise<any>;
|
||||||
|
getSchemaChildren: (schema: ISchema) => any;
|
||||||
__beforeInit: (props: IRendererProps) => any;
|
__beforeInit: (props: IRendererProps) => any;
|
||||||
__init: (props: IRendererProps) => any;
|
__init: (props: IRendererProps) => any;
|
||||||
__afterInit: (props: IRendererProps) => any;
|
__afterInit: (props: IRendererProps) => any;
|
||||||
reloadDataSource: () => Promise<any>;
|
|
||||||
__setLifeCycleMethods: (method: string, args?: any) => any;
|
__setLifeCycleMethods: (method: string, args?: any) => any;
|
||||||
__bindCustomMethods: (props: IRendererProps) => any;
|
__bindCustomMethods: (props: IRendererProps) => any;
|
||||||
__generateCtx: (ctx: object) => any;
|
__generateCtx: (ctx: object) => any;
|
||||||
@ -113,7 +128,8 @@ export interface IRenderer {
|
|||||||
__initDataSource: (props: IRendererProps) => any;
|
__initDataSource: (props: IRendererProps) => any;
|
||||||
__render: () => any;
|
__render: () => any;
|
||||||
__getRef: (ref: any) => any;
|
__getRef: (ref: any) => any;
|
||||||
getSchemaChildren: (schema: ISchema) => any;
|
__getSchemaChildrenVirtualDom: (schema: ISchema, Comp: any, nodeChildrenMap?: Map<string, any>) => any;
|
||||||
|
__getComponentProps: (schema: ISchema, Comp: any, componentInfo: any) => any;
|
||||||
__createDom: () => any;
|
__createDom: () => any;
|
||||||
__createVirtualDom: (schema: any, self: any, parentInfo: IInfo, idx: string | number) => any;
|
__createVirtualDom: (schema: any, self: any, parentInfo: IInfo, idx: string | number) => any;
|
||||||
__createLoopVirtualDom: (schema: any, self: any, parentInfo: IInfo, idx: number | string) => any;
|
__createLoopVirtualDom: (schema: any, self: any, parentInfo: IInfo, idx: number | string) => any;
|
||||||
|
|||||||
@ -13,8 +13,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ali/lowcode-datasource-types": "^1.0.21",
|
"@ali/lowcode-datasource-types": "^1.0.21",
|
||||||
|
"@ali/lowcode-designer": "^1.0.65",
|
||||||
"power-di": "^2.2.4",
|
"power-di": "^2.2.4",
|
||||||
"react": "^16"
|
"react": "^16",
|
||||||
|
"strict-event-emitter-types": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@alib/build-scripts": "^0.1.18",
|
"@alib/build-scripts": "^0.1.18",
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
import StrictEventEmitter from 'strict-event-emitter-types';
|
||||||
import { ReactNode, ComponentType } from 'react';
|
import { ReactNode, ComponentType } from 'react';
|
||||||
import { NpmInfo } from './npm';
|
|
||||||
import { RegisterOptions } from 'power-di';
|
import { RegisterOptions } from 'power-di';
|
||||||
|
import { NpmInfo } from './npm';
|
||||||
|
import * as GlobalEvent from './event';
|
||||||
|
|
||||||
export type KeyType = (new (...args: any[]) => any) | symbol | string;
|
export type KeyType = (new (...args: any[]) => any) | symbol | string;
|
||||||
export type ClassType = new (...args: any[]) => any;
|
export type ClassType = new (...args: any[]) => any;
|
||||||
@ -17,7 +19,7 @@ export type GetReturnType<T, ClsType> = T extends undefined
|
|||||||
: any
|
: any
|
||||||
: T;
|
: T;
|
||||||
|
|
||||||
export interface IEditor extends EventEmitter {
|
export interface IEditor extends StrictEventEmitter<EventEmitter, GlobalEvent.EventConfig> {
|
||||||
get<T = undefined, KeyOrType = any>(keyOrType: KeyOrType, opt?: GetOptions): GetReturnType<T, KeyOrType> | undefined;
|
get<T = undefined, KeyOrType = any>(keyOrType: KeyOrType, opt?: GetOptions): GetReturnType<T, KeyOrType> | undefined;
|
||||||
|
|
||||||
has(keyOrType: KeyType): boolean;
|
has(keyOrType: KeyType): boolean;
|
||||||
|
|||||||
10
packages/types/src/event/index.ts
Normal file
10
packages/types/src/event/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import * as Node from './node';
|
||||||
|
|
||||||
|
export interface EventConfig {
|
||||||
|
[Node.Prop.Change]: (options: Node.Prop.ChangeOptions) => any;
|
||||||
|
[Node.Prop.InnerChange]: (options: Node.Prop.ChangeOptions) => any;
|
||||||
|
[Node.Rerender]: (options: Node.RerenderOptions) => void;
|
||||||
|
[eventName: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export * as Node from './node';
|
||||||
10
packages/types/src/event/node.ts
Normal file
10
packages/types/src/event/node.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export * as Prop from './prop';
|
||||||
|
|
||||||
|
export interface RerenderOptions {
|
||||||
|
time: number;
|
||||||
|
componentName?: string;
|
||||||
|
type?: string;
|
||||||
|
nodeCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Rerender = 'node.edit.rerender.time';
|
||||||
18
packages/types/src/event/prop.ts
Normal file
18
packages/types/src/event/prop.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { IPropParent, SettingEntry } from '@ali/lowcode-designer';
|
||||||
|
|
||||||
|
export interface ChangeOptions {
|
||||||
|
key?: string | number;
|
||||||
|
prop?: SettingEntry | IPropParent;
|
||||||
|
node: Node;
|
||||||
|
newValue: any;
|
||||||
|
oldValue: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node Prop 变化事件
|
||||||
|
* @Deprecated Please Replace With InnerPropChange
|
||||||
|
*/
|
||||||
|
export const Change = 'node.prop.change';
|
||||||
|
|
||||||
|
/** Node Prop 变化事件 */
|
||||||
|
export const InnerChange = 'node.innerProp.change';
|
||||||
@ -19,3 +19,4 @@ export * from './transform-stage';
|
|||||||
export * from './code-intermediate';
|
export * from './code-intermediate';
|
||||||
export * from './code-result';
|
export * from './code-result';
|
||||||
export * from './assets';
|
export * from './assets';
|
||||||
|
export * as GlobalEvent from './event';
|
||||||
|
|||||||
@ -55,14 +55,6 @@ export function waitForThing(obj: any, path: string): Promise<any> {
|
|||||||
return _innerWaitForThing(obj, path);
|
return _innerWaitForThing(obj, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断当前 meta 是否从 vc prototype 转换而来
|
|
||||||
* @param meta
|
|
||||||
*/
|
|
||||||
export function isFromVC(meta: ComponentMeta) {
|
|
||||||
return !!meta?.getMetadata()?.experimental;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function arrShallowEquals(arr1: any[], arr2: any[]): boolean {
|
export function arrShallowEquals(arr1: any[], arr2: any[]): boolean {
|
||||||
if (!Array.isArray(arr1) || !Array.isArray(arr2)) return false;
|
if (!Array.isArray(arr1) || !Array.isArray(arr2)) return false;
|
||||||
if (arr1.length !== arr2.length) return false;
|
if (arr1.length !== arr2.length) return false;
|
||||||
|
|||||||
@ -22,6 +22,7 @@
|
|||||||
"@ali/lowcode-editor-core": "1.0.67",
|
"@ali/lowcode-editor-core": "1.0.67",
|
||||||
"@ali/lowcode-editor-skeleton": "1.0.67",
|
"@ali/lowcode-editor-skeleton": "1.0.67",
|
||||||
"@ali/lowcode-utils": "1.0.67",
|
"@ali/lowcode-utils": "1.0.67",
|
||||||
|
"@ali/lowcode-types": "1.0.67",
|
||||||
"@ali/ve-i18n-util": "^2.0.0",
|
"@ali/ve-i18n-util": "^2.0.0",
|
||||||
"@ali/ve-icons": "^4.1.9",
|
"@ali/ve-icons": "^4.1.9",
|
||||||
"@ali/ve-less-variables": "2.0.3",
|
"@ali/ve-less-variables": "2.0.3",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import logger from '@ali/vu-logger';
|
import logger from '@ali/vu-logger';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { editor } from '@ali/lowcode-engine';
|
import { editor } from '@ali/lowcode-engine';
|
||||||
import { isJSExpression } from '@ali/lowcode-types';
|
import { GlobalEvent, isJSExpression } from '@ali/lowcode-types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bus class as an EventEmitter
|
* Bus class as an EventEmitter
|
||||||
@ -87,7 +87,7 @@ function triggerUseVariableChange(data: any) {
|
|||||||
propConfig.useVariableChange.call(prop, { isUseVariable: true });
|
propConfig.useVariableChange.call(prop, { isUseVariable: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
editor?.on('node.prop.change', (data) => {
|
editor?.on(GlobalEvent.Node.Prop.Change, (data) => {
|
||||||
bus.emit('node.prop.change', data);
|
bus.emit('node.prop.change', data);
|
||||||
|
|
||||||
triggerUseVariableChange(data);
|
triggerUseVariableChange(data);
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { Node } from '@ali/lowcode-designer';
|
||||||
|
|
||||||
declare enum LANGUAGES {
|
declare enum LANGUAGES {
|
||||||
zh_CN = 'zh_CN',
|
zh_CN = 'zh_CN',
|
||||||
en_US = 'en_US'
|
en_US = 'en_US'
|
||||||
@ -62,7 +64,10 @@ export interface II18nUtil {
|
|||||||
* @param key
|
* @param key
|
||||||
* @param lang
|
* @param lang
|
||||||
*/
|
*/
|
||||||
get(key: string, lang: string): string | I18nRecord;
|
get(key: string, lang: string, info?: {
|
||||||
|
node?: Node,
|
||||||
|
path?: string,
|
||||||
|
}): string | I18nRecord;
|
||||||
getFromRemote(key: string): Promise<I18nRecord>;
|
getFromRemote(key: string): Promise<I18nRecord>;
|
||||||
getItem(key: string, forceData?: boolean): any;
|
getItem(key: string, forceData?: boolean): any;
|
||||||
getItems(): I18nRecord[];
|
getItems(): I18nRecord[];
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
import { editorCabin, editor } from '@ali/lowcode-engine';
|
||||||
import { editorCabin } from '@ali/lowcode-engine';
|
import lodashGet from 'lodash.get';
|
||||||
|
import { GlobalEvent } from '@ali/lowcode-types';
|
||||||
|
|
||||||
const { obx } = editorCabin;
|
const { obx } = editorCabin;
|
||||||
|
|
||||||
@ -23,6 +24,33 @@ class DocItem {
|
|||||||
...strings,
|
...strings,
|
||||||
});
|
});
|
||||||
this.emitter = new EventEmitter();
|
this.emitter = new EventEmitter();
|
||||||
|
this.nodeList = new Map();
|
||||||
|
this.onChange((doc, oldDoc) => {
|
||||||
|
if (this.nodeList.size <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.nodeList.forEach(({
|
||||||
|
node,
|
||||||
|
path,
|
||||||
|
}) => {
|
||||||
|
const prop = node.settingEntry.getProp(path);
|
||||||
|
const changeInfo = {
|
||||||
|
key: prop?.key,
|
||||||
|
prop,
|
||||||
|
newValue: doc,
|
||||||
|
oldValue: oldDoc,
|
||||||
|
};
|
||||||
|
node.emitPropChange(changeInfo);
|
||||||
|
editor.emit(GlobalEvent.Node.Prop.Change, {
|
||||||
|
node,
|
||||||
|
...changeInfo,
|
||||||
|
});
|
||||||
|
editor.emit(GlobalEvent.Node.Prop.InnerChange, {
|
||||||
|
node,
|
||||||
|
...changeInfo,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
this.inited = unInitial !== true;
|
this.inited = unInitial !== true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,17 +62,18 @@ class DocItem {
|
|||||||
if (lang) {
|
if (lang) {
|
||||||
return this.doc[lang];
|
return this.doc[lang];
|
||||||
}
|
}
|
||||||
return this.doc;
|
return Object.assign({}, this.doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
setDoc(doc, lang, initial) {
|
setDoc(doc, lang, initial) {
|
||||||
|
const oldValue = Object.assign({}, this.doc);
|
||||||
if (lang) {
|
if (lang) {
|
||||||
this.doc[lang] = doc;
|
this.doc[lang] = doc;
|
||||||
} else {
|
} else {
|
||||||
const { use, strings } = doc || {};
|
const { use, strings } = doc || {};
|
||||||
Object.assign(this.doc, strings);
|
Object.assign(this.doc, doc, strings);
|
||||||
}
|
}
|
||||||
this.emitter.emit('change', this.doc);
|
this.emitter.emit('change', this.doc, oldValue);
|
||||||
|
|
||||||
if (initial) {
|
if (initial) {
|
||||||
this.inited = true;
|
this.inited = true;
|
||||||
@ -53,6 +82,14 @@ class DocItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
collectNode = (nodeInfo) => {
|
||||||
|
const key = lodashGet(nodeInfo, 'node.id');
|
||||||
|
if (!key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.nodeList.set(key, nodeInfo);
|
||||||
|
};
|
||||||
|
|
||||||
remove() {
|
remove() {
|
||||||
if (!this.inited) return Promise.reject('not initialized');
|
if (!this.inited) return Promise.reject('not initialized');
|
||||||
|
|
||||||
@ -135,6 +172,13 @@ class I18nUtil {
|
|||||||
item = this.fullList.find(doc => doc.getKey() === key);
|
item = this.fullList.find(doc => doc.getKey() === key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!item && (this.maps[key] || this.fullMap[key])) {
|
||||||
|
// 如果从 items 和 fullList 里面找到 DocItem,就从 maps/fullMap 里面取
|
||||||
|
item = this.maps[key] || this.fullMap[key];
|
||||||
|
this.items.unshift(item);
|
||||||
|
this.fullList.unshift(item);
|
||||||
|
}
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
item.setDoc(dict);
|
item.setDoc(dict);
|
||||||
} else {
|
} else {
|
||||||
@ -228,9 +272,10 @@ class I18nUtil {
|
|||||||
return this._load(configs);
|
return this._load(configs);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key, lang) {
|
get(key, lang, nodeInfo) {
|
||||||
const item = this.getItem(key);
|
const item = this.getItem(key);
|
||||||
if (item) {
|
if (item) {
|
||||||
|
item.collectNode(nodeInfo);
|
||||||
return item.getDoc(lang);
|
return item.getDoc(lang);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -8,7 +8,21 @@ import { isVariable } from '../utils';
|
|||||||
|
|
||||||
// FIXME: 表达式使用 mock 值,未来live 模式直接使用原始值
|
// FIXME: 表达式使用 mock 值,未来live 模式直接使用原始值
|
||||||
// TODO: designType
|
// TODO: designType
|
||||||
export function deepValueParser(obj: any, node: Node): any {
|
export function valueParser(obj: any, node: Node): any {
|
||||||
|
return deepValueParser(obj, {
|
||||||
|
node,
|
||||||
|
path: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepValueParser(obj: any, info: {
|
||||||
|
node: Node;
|
||||||
|
path?: string;
|
||||||
|
}): any {
|
||||||
|
const {
|
||||||
|
path = '',
|
||||||
|
node,
|
||||||
|
} = info;
|
||||||
// 如果不是 vc 体系,不做这个兼容处理
|
// 如果不是 vc 体系,不做这个兼容处理
|
||||||
if (!node.componentMeta.prototype) {
|
if (!node.componentMeta.prototype) {
|
||||||
return obj;
|
return obj;
|
||||||
@ -34,14 +48,17 @@ export function deepValueParser(obj: any, node: Node): any {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
return obj.map((item) => deepValueParser(item, node));
|
return obj.map((item) => deepValueParser(item, { node }));
|
||||||
}
|
}
|
||||||
if (isPlainObject(obj)) {
|
if (isPlainObject(obj)) {
|
||||||
if (isI18nData(obj)) {
|
if (isI18nData(obj)) {
|
||||||
// FIXME! use editor.get
|
// FIXME! use editor.get
|
||||||
let locale = Env.getLocale();
|
let locale = Env.getLocale();
|
||||||
if (obj.key && i18nUtil.get(obj.key, locale)) {
|
if (obj.key && i18nUtil.get(obj.key, locale)) {
|
||||||
return i18nUtil.get(obj.key, locale);
|
return i18nUtil.get(obj.key, locale, {
|
||||||
|
node,
|
||||||
|
path,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (locale !== 'zh_CN' && locale !== 'zh_TW' && !obj[locale]) {
|
if (locale !== 'zh_CN' && locale !== 'zh_TW' && !obj[locale]) {
|
||||||
locale = 'en_US';
|
locale = 'en_US';
|
||||||
@ -54,7 +71,10 @@ export function deepValueParser(obj: any, node: Node): any {
|
|||||||
}
|
}
|
||||||
const out: any = {};
|
const out: any = {};
|
||||||
Object.keys(obj).forEach((key) => {
|
Object.keys(obj).forEach((key) => {
|
||||||
out[key] = deepValueParser(obj[key], node);
|
out[key] = deepValueParser(obj[key], {
|
||||||
|
node,
|
||||||
|
path: path ? `${path}.${key}` : key,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@ -2,7 +2,7 @@ import { editor, designer, designerCabin } from '@ali/lowcode-engine';
|
|||||||
import bus from './bus';
|
import bus from './bus';
|
||||||
import { VE_EVENTS } from './base/const';
|
import { VE_EVENTS } from './base/const';
|
||||||
|
|
||||||
import { deepValueParser } from './props-reducers/deep-value-reducer';
|
import { valueParser } from './props-reducers/value-parser';
|
||||||
import { liveEditingRule, liveEditingSaveHander } from './vc-live-editing';
|
import { liveEditingRule, liveEditingSaveHander } from './vc-live-editing';
|
||||||
import {
|
import {
|
||||||
compatibleReducer,
|
compatibleReducer,
|
||||||
@ -49,7 +49,7 @@ designer.addPropsReducer(upgradePageLifeCyclesReducer, TransformStage.Save);
|
|||||||
// 设计器组件样式处理
|
// 设计器组件样式处理
|
||||||
designer.addPropsReducer(stylePropsReducer, TransformStage.Render);
|
designer.addPropsReducer(stylePropsReducer, TransformStage.Render);
|
||||||
// 国际化 & Expression 渲染时处理
|
// 国际化 & Expression 渲染时处理
|
||||||
designer.addPropsReducer(deepValueParser, TransformStage.Render);
|
designer.addPropsReducer(valueParser, TransformStage.Render);
|
||||||
|
|
||||||
// Init 的时候没有拿到 dataSource, 只能在 Render 和 Save 的时候都调用一次,理论上执行时机在 Init
|
// Init 的时候没有拿到 dataSource, 只能在 Render 和 Save 的时候都调用一次,理论上执行时机在 Init
|
||||||
// Render 和 Save 都要各调用一次,感觉也是有问题的,是不是应该在 Render 执行一次就行了?见上 filterReducer 也是一样的处理方式。
|
// Render 和 Save 都要各调用一次,感觉也是有问题的,是不是应该在 Render 执行一次就行了?见上 filterReducer 也是一样的处理方式。
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user