packages/renderer-core/src/hoc/leaf.tsx (480 lines of code) (raw):
import { INode, IPublicTypePropChangeOptions } from '@alilc/lowcode-designer';
import { GlobalEvent, IPublicEnumTransformStage, IPublicTypeNodeSchema, IPublicTypeEngineOptions } from '@alilc/lowcode-types';
import { isReactComponent, cloneEnumerableProperty } from '@alilc/lowcode-utils';
import { debounce } from '../utils/common';
import adapter from '../adapter';
import * as types from '../types/index';
import logger from '../utils/logger';
export interface IComponentHocInfo {
schema: any;
baseRenderer: types.IBaseRendererInstance;
componentInfo: any;
scope: any;
}
export interface IComponentHocProps {
__tag: any;
componentId: any;
_leaf: any;
forwardedRef?: any;
}
export interface IComponentHocState {
childrenInState: boolean;
nodeChildren: any;
nodeCacheProps: any;
/** 控制是否显示隐藏 */
visible: boolean;
/** 控制是否渲染 */
condition: boolean;
nodeProps: any;
}
type DesignMode = Pick<IPublicTypeEngineOptions, 'designMode'>['designMode'];
export interface IComponentHoc {
designMode: DesignMode | DesignMode[];
hoc: IComponentConstruct;
}
export type IComponentConstruct = (Comp: types.IBaseRenderComponent, info: IComponentHocInfo) => types.IGeneralConstructor;
interface IProps {
_leaf: INode | undefined;
visible: boolean;
componentId: number;
children?: INode[];
__tag: number;
forwardedRef?: any;
}
enum RerenderType {
All = 'All',
ChildChanged = 'ChildChanged',
PropsChanged = 'PropsChanged',
VisibleChanged = 'VisibleChanged',
MinimalRenderUnit = 'MinimalRenderUnit',
}
// 缓存 Leaf 层组件,防止重新渲染问题
class LeafCache {
/** 组件缓存 */
component = new Map();
/**
* 状态缓存,场景:属性变化后,改组件被销毁,state 为空,没有展示修改后的属性
*/
state = new Map();
/**
* 订阅事件缓存,导致 rerender 的订阅事件
*/
event = new Map();
ref = new Map();
constructor(public documentId: string, public device: string) {
}
}
let cache: LeafCache;
/** 部分没有渲染的 node 节点进行兜底处理 or 渲染方式没有渲染 LeafWrapper */
function initRerenderEvent({
schema,
__debug,
container,
getNode,
}: any) {
const leaf = getNode?.(schema.id);
if (!leaf
|| cache.event.get(schema.id)?.clear
|| leaf === cache.event.get(schema.id)
) {
return;
}
cache.event.get(schema.id)?.dispose.forEach((disposeFn: any) => disposeFn && disposeFn());
const debounceRerender = debounce(() => {
container.rerender();
}, 20);
cache.event.set(schema.id, {
clear: false,
leaf,
dispose: [
leaf?.onPropChange?.(() => {
if (!container.autoRepaintNode) {
return;
}
__debug(`${schema.componentName}[${schema.id}] leaf not render in SimulatorRendererView, leaf onPropsChange make rerender`);
debounceRerender();
}),
leaf?.onChildrenChange?.(() => {
if (!container.autoRepaintNode) {
return;
}
__debug(`${schema.componentName}[${schema.id}] leaf not render in SimulatorRendererView, leaf onChildrenChange make rerender`);
debounceRerender();
}) as Function,
leaf?.onVisibleChange?.(() => {
if (!container.autoRepaintNode) {
return;
}
__debug(`${schema.componentName}[${schema.id}] leaf not render in SimulatorRendererView, leaf onVisibleChange make rerender`);
debounceRerender();
}),
],
});
}
/** 渲染的 node 节点全局注册事件清除 */
function clearRerenderEvent(id: string): void {
if (cache.event.get(id)?.clear) {
return;
}
cache.event.get(id)?.dispose?.forEach((disposeFn: any) => disposeFn && disposeFn());
cache.event.set(id, {
clear: true,
dispose: [],
});
}
// 给每个组件包裹一个 HOC Leaf,支持组件内部属性变化,自响应渲染
export function leafWrapper(Comp: types.IBaseRenderComponent, {
schema,
baseRenderer,
componentInfo,
scope,
}: IComponentHocInfo) {
const {
__debug,
__getComponentProps: getProps,
__getSchemaChildrenVirtualDom: getChildren,
__parseData,
} = baseRenderer;
const { engine } = baseRenderer.context;
const host = baseRenderer.props?.__host;
const curDocumentId = baseRenderer.props?.documentId ?? '';
const curDevice = baseRenderer.props?.device ?? '';
const getNode = baseRenderer.props?.getNode;
const container = baseRenderer.props?.__container;
const setSchemaChangedSymbol = baseRenderer.props?.setSchemaChangedSymbol;
const editor = host?.designer?.editor;
const runtime = adapter.getRuntime();
const { forwardRef, createElement } = runtime;
const Component = runtime.Component as types.IGeneralConstructor<
IComponentHocProps, IComponentHocState
>;
const componentCacheId = schema.id;
if (!cache || (curDocumentId && curDocumentId !== cache.documentId) || (curDevice && curDevice !== cache.device)) {
cache?.event.forEach(event => {
event.dispose?.forEach((disposeFn: any) => disposeFn && disposeFn());
});
cache = new LeafCache(curDocumentId, curDevice);
}
if (!isReactComponent(Comp)) {
logger.error(`${schema.componentName} component may be has errors: `, Comp);
}
initRerenderEvent({
schema,
__debug,
container,
getNode,
});
if (curDocumentId && cache.component.has(componentCacheId) && (cache.component.get(componentCacheId).Comp === Comp)) {
return cache.component.get(componentCacheId).LeafWrapper;
}
class LeafHoc extends Component {
recordInfo: {
startTime?: number | null;
type?: string;
node?: INode;
} = {};
private curEventLeaf: INode | undefined;
static displayName = schema.componentName;
disposeFunctions: Array<((() => void) | Function)> = [];
__component_tag = 'leafWrapper';
renderUnitInfo: {
minimalUnitId?: string;
minimalUnitName?: string;
singleRender?: boolean;
};
// 最小渲染单元做防抖处理
makeUnitRenderDebounced = debounce(() => {
this.beforeRender(RerenderType.MinimalRenderUnit);
const schema = this.leaf?.export?.(IPublicEnumTransformStage.Render);
if (!schema) {
return;
}
const nextProps = getProps(schema, scope, Comp, componentInfo);
const children = getChildren(schema, scope, Comp);
const nextState = {
nodeProps: nextProps,
nodeChildren: children,
childrenInState: true,
};
if ('children' in nextProps) {
nextState.nodeChildren = nextProps.children;
}
__debug(`${this.leaf?.componentName}(${this.props.componentId}) MinimalRenderUnit Render!`);
this.setState(nextState);
}, 20);
constructor(props: IProps, context: any) {
super(props, context);
// 监听以下事件,当变化时更新自己
__debug(`${schema.componentName}[${this.props.componentId}] leaf render in SimulatorRendererView`);
clearRerenderEvent(componentCacheId);
this.curEventLeaf = this.leaf;
cache.ref.set(componentCacheId, {
makeUnitRender: this.makeUnitRender,
});
let cacheState = cache.state.get(componentCacheId);
if (!cacheState || cacheState.__tag !== props.__tag) {
cacheState = this.getDefaultState(props);
}
this.state = cacheState;
}
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?.eventBus.emit(GlobalEvent.Node.Rerender, {
componentName,
time: endTime - this.recordInfo.startTime,
type: this.recordInfo.type,
nodeCount,
});
this.recordInfo.startTime = null;
};
makeUnitRender = () => {
this.makeUnitRenderDebounced();
};
get autoRepaintNode() {
return container?.autoRepaintNode;
}
componentDidUpdate() {
this.recordTime();
}
componentDidMount() {
const _leaf = this.leaf;
this.initOnPropsChangeEvent(_leaf);
this.initOnChildrenChangeEvent(_leaf);
this.initOnVisibleChangeEvent(_leaf);
this.recordTime();
}
getDefaultState(nextProps: any) {
const {
hidden = false,
condition = true,
} = nextProps.__inner__ || this.leaf?.export?.(IPublicEnumTransformStage.Render) || {};
return {
nodeChildren: null,
childrenInState: false,
visible: !hidden,
condition: __parseData?.(condition, scope),
nodeCacheProps: {},
nodeProps: {},
};
}
setState(state: any) {
cache.state.set(componentCacheId, {
...this.state,
...state,
__tag: this.props.__tag,
});
super.setState(state);
}
/** 由于内部属性变化,在触发渲染前,会执行该函数 */
beforeRender(type: string, node?: INode): void {
this.recordInfo.startTime = Date.now();
this.recordInfo.type = type;
this.recordInfo.node = node;
setSchemaChangedSymbol?.(true);
}
judgeMiniUnitRender() {
if (!this.renderUnitInfo) {
this.getRenderUnitInfo();
}
const renderUnitInfo = this.renderUnitInfo || {
singleRender: true,
};
if (renderUnitInfo.singleRender) {
return;
}
const ref = cache.ref.get(renderUnitInfo.minimalUnitId);
if (!ref) {
__debug('Cant find minimalRenderUnit ref! This make rerender!');
container?.rerender();
return;
}
__debug(`${this.leaf?.componentName}(${this.props.componentId}) need render, make its minimalRenderUnit ${renderUnitInfo.minimalUnitName}(${renderUnitInfo.minimalUnitId})`);
ref.makeUnitRender();
}
getRenderUnitInfo(leaf = this.leaf) {
// leaf 在低代码组件中存在 mock 的情况,退出最小渲染单元判断
if (!leaf || typeof leaf.isRoot !== 'function') {
return;
}
if (leaf.isRootNode) {
this.renderUnitInfo = {
singleRender: true,
...(this.renderUnitInfo || {}),
};
}
if (leaf.componentMeta.isMinimalRenderUnit) {
this.renderUnitInfo = {
minimalUnitId: leaf.id,
minimalUnitName: leaf.componentName,
singleRender: false,
};
}
if (leaf.hasLoop()) {
// 含有循环配置的元素,父元素是最小渲染单元
this.renderUnitInfo = {
minimalUnitId: leaf?.parent?.id,
minimalUnitName: leaf?.parent?.componentName,
singleRender: false,
};
}
if (leaf.parent) {
this.getRenderUnitInfo(leaf.parent);
}
}
componentWillReceiveProps(nextProps: any) {
let { componentId } = nextProps;
if (nextProps.__tag === this.props.__tag) {
return null;
}
const _leaf = getNode?.(componentId);
if (_leaf && this.curEventLeaf && _leaf !== this.curEventLeaf) {
this.disposeFunctions.forEach((fn) => fn());
this.disposeFunctions = [];
this.initOnChildrenChangeEvent(_leaf);
this.initOnPropsChangeEvent(_leaf);
this.initOnVisibleChangeEvent(_leaf);
this.curEventLeaf = _leaf;
}
const {
visible,
...resetState
} = this.getDefaultState(nextProps);
this.setState(resetState);
}
/** 监听参数变化 */
initOnPropsChangeEvent(leaf = this.leaf): void {
const handlePropsChange = debounce((propChangeInfo: IPublicTypePropChangeOptions) => {
const {
key,
newValue = null,
} = propChangeInfo;
const node = leaf;
if (key === '___condition___') {
const { condition = true } = this.leaf?.export(IPublicEnumTransformStage.Render) || {};
const conditionValue = __parseData?.(condition, scope);
__debug(`key is ___condition___, change condition value to [${condition}]`);
// 条件表达式改变
this.setState({
condition: conditionValue,
});
return;
}
// 如果循坏条件变化,从根节点重新渲染
// 目前多层循坏无法判断需要从哪一层开始渲染,故先粗暴解决
if (key === '___loop___') {
__debug('key is ___loop___, render a page!');
container?.rerender();
// 由于 scope 变化,需要清空缓存,使用新的 scope
cache.component.delete(componentCacheId);
return;
}
this.beforeRender(RerenderType.PropsChanged);
const { state } = this;
const { nodeCacheProps } = state;
const nodeProps = getProps(node?.export?.(IPublicEnumTransformStage.Render) as IPublicTypeNodeSchema, scope, Comp, componentInfo);
if (key && !(key in nodeProps) && (key in this.props)) {
// 当 key 在 this.props 中时,且不存在在计算值中,需要用 newValue 覆盖掉 this.props 的取值
nodeCacheProps[key] = newValue;
}
__debug(`${leaf?.componentName}[${this.props.componentId}] component trigger onPropsChange!`, nodeProps, nodeCacheProps, key, newValue);
this.setState('children' in nodeProps ? {
nodeChildren: nodeProps.children,
nodeProps,
childrenInState: true,
nodeCacheProps,
} : {
nodeProps,
nodeCacheProps,
});
this.judgeMiniUnitRender();
});
const dispose = leaf?.onPropChange?.((propChangeInfo: IPublicTypePropChangeOptions) => {
if (!this.autoRepaintNode) {
return;
}
handlePropsChange(propChangeInfo);
});
dispose && this.disposeFunctions.push(dispose);
}
/**
* 监听显隐变化
*/
initOnVisibleChangeEvent(leaf = this.leaf) {
const dispose = leaf?.onVisibleChange?.((flag: boolean) => {
if (!this.autoRepaintNode) {
return;
}
if (this.state.visible === flag) {
return;
}
__debug(`${leaf?.componentName}[${this.props.componentId}] component trigger onVisibleChange(${flag}) event`);
this.beforeRender(RerenderType.VisibleChanged);
this.setState({
visible: flag,
});
this.judgeMiniUnitRender();
});
dispose && this.disposeFunctions.push(dispose);
}
/**
* 监听子元素变化(拖拽,删除...)
*/
initOnChildrenChangeEvent(leaf = this.leaf) {
const dispose = leaf?.onChildrenChange?.((param): void => {
if (!this.autoRepaintNode) {
return;
}
const {
type,
node,
} = param || {};
this.beforeRender(`${RerenderType.ChildChanged}-${type}`, node);
// TODO: 缓存同级其他元素的 children。
// 缓存二级 children Next 查询筛选组件有问题
// 缓存一级 children Next Tab 组件有问题
const nextChild = getChildren(leaf?.export?.(IPublicEnumTransformStage.Render) as types.ISchema, scope, Comp);
__debug(`${schema.componentName}[${this.props.componentId}] component trigger onChildrenChange event`, nextChild);
this.setState({
nodeChildren: nextChild,
childrenInState: true,
});
this.judgeMiniUnitRender();
});
dispose && this.disposeFunctions.push(dispose);
}
componentWillUnmount() {
this.disposeFunctions.forEach(fn => fn());
}
get hasChildren(): boolean {
if (!this.state.childrenInState) {
return 'children' in this.props;
}
return true;
}
get children(): any {
if (this.state.childrenInState) {
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 this.props.children;
}
get leaf(): INode | undefined {
if (this.props._leaf?.isMock) {
// 低代码组件作为一个整体更新,其内部的组件不需要监听相关事件
return undefined;
}
return getNode?.(componentCacheId);
}
render() {
if (!this.state.visible || !this.state.condition) {
return null;
}
const {
forwardedRef,
...rest
} = this.props;
const compProps = {
...rest,
...(this.state.nodeCacheProps || {}),
...(this.state.nodeProps || {}),
children: [],
__id: this.props.componentId,
ref: forwardedRef,
};
delete compProps.__inner__;
if (this.hasChildren) {
return engine.createElement(Comp, compProps, this.children);
}
return engine.createElement(Comp, compProps);
}
}
let LeafWrapper = forwardRef((props: any, ref: any) => {
return createElement(LeafHoc, {
...props,
forwardedRef: ref,
});
});
LeafWrapper = cloneEnumerableProperty(LeafWrapper, Comp);
LeafWrapper.displayName = (Comp as any).displayName;
cache.component.set(componentCacheId, {
LeafWrapper,
Comp,
});
return LeafWrapper;
}