feat: the renderer-core leaf component removes the react.createElement call

This commit is contained in:
liujuping 2023-02-07 17:18:47 +08:00 committed by 林熠
parent 0184dcd938
commit c3ce042c83
10 changed files with 160 additions and 98 deletions

View File

@ -3,6 +3,9 @@ import { IPublicTypeNodeSchema, IPublicTypeComponentInstance, IPublicTypeNodeIns
export interface BuiltinSimulatorRenderer {
readonly isSimulatorRenderer: true;
autoRepaintNode?: boolean;
components: Record<string, Component>;
rerender: () => void;
createComponent(schema: IPublicTypeNodeSchema): Component | null;
getComponent(componentName: string): Component;
getClosestNodeInstance(

View File

@ -64,6 +64,7 @@ export interface IDocumentModel extends Omit< IPublicModelDocumentModel, 'select
dragObject: IPublicTypeDragNodeObject | IPublicTypeNodeSchema | INode | IPublicTypeDragNodeDataObject,
): boolean;
getNodeCount(): number;
}
export class DocumentModel implements IDocumentModel {

View File

@ -15,6 +15,7 @@ import {
IPublicModelNode,
IPublicModelExclusiveGroup,
IPublicEnumTransformStage,
IPublicTypeDisposable,
} from '@alilc/lowcode-types';
import { compatStage, isDOMText, isJSExpression, isNode } from '@alilc/lowcode-utils';
import { SettingTopEntry } from '@alilc/lowcode-designer';
@ -112,6 +113,10 @@ export interface INode extends IPublicModelNode {
replaceChild(node: INode, data: any): INode;
getSuitablePlace(node: INode, ref: any): any;
onChildrenChange(fn: (param?: { type: string; node: INode }) => void): IPublicTypeDisposable;
onPropChange(func: (info: IPublicTypePropChangeOptions) => void): IPublicTypeDisposable;
}
/**
@ -1080,7 +1085,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
return this.props;
}
onChildrenChange(fn: (param?: { type: string; node: Node }) => void): (() => void) | undefined {
onChildrenChange(fn: (param?: { type: string; node: Node }) => void): IPublicTypeDisposable {
const wrappedFunc = wrapWithEventSwitch(fn);
return this.children?.onChange(wrappedFunc);
}
@ -1273,7 +1278,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
this.emitter?.emit('propChange', val);
}
onPropChange(func: (info: IPublicTypePropChangeOptions) => void): Function {
onPropChange(func: (info: IPublicTypePropChangeOptions) => void): IPublicTypeDisposable {
const wrappedFunc = wrapWithEventSwitch(func);
this.emitter.on('propChange', wrappedFunc);
return () => {

View File

@ -1,4 +1,4 @@
import { BuiltinSimulatorHost, Node, IPublicTypePropChangeOptions } from '@alilc/lowcode-designer';
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';
@ -16,15 +16,17 @@ export interface IComponentHocProps {
__tag: any;
componentId: any;
_leaf: any;
forwardedRef: any;
forwardedRef?: any;
}
export interface IComponentHocState {
childrenInState: boolean;
nodeChildren: any;
nodeCacheProps: any;
/** 控制是否显示隐藏 */
visible: boolean;
/** 控制是否渲染 */
condition: boolean;
nodeProps: any;
@ -40,15 +42,17 @@ export interface IComponentHoc {
export type IComponentConstruct = (Comp: types.IBaseRenderComponent, info: IComponentHocInfo) => types.IGeneralConstructor;
interface IProps {
_leaf: Node | undefined;
_leaf: INode | undefined;
visible: boolean;
componentId?: number;
componentId: number;
children?: Node[];
children?: INode[];
__tag?: number;
__tag: number;
forwardedRef?: any;
}
enum RerenderType {
@ -61,8 +65,7 @@ enum RerenderType {
// 缓存 Leaf 层组件,防止重新渲染问题
class LeafCache {
constructor(public documentId: string, public device: string) {
}
/** 组件缓存 */
component = new Map();
@ -77,6 +80,9 @@ class LeafCache {
event = new Map();
ref = new Map();
constructor(public documentId: string, public device: string) {
}
}
let cache: LeafCache;
@ -155,11 +161,11 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, {
const curDocumentId = baseRenderer.props?.documentId ?? '';
const curDevice = baseRenderer.props?.device ?? '';
const getNode = baseRenderer.props?.getNode;
const container: BuiltinSimulatorHost = baseRenderer.props?.__container;
const container = baseRenderer.props?.__container;
const setSchemaChangedSymbol = baseRenderer.props?.setSchemaChangedSymbol;
const editor = host?.designer?.editor;
const runtime = adapter.getRuntime();
const { forwardRef } = runtime;
const { forwardRef, createElement } = runtime;
const Component = runtime.Component as types.IGeneralConstructor<
IComponentHocProps, IComponentHocState
>;
@ -192,14 +198,64 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, {
recordInfo: {
startTime?: number | null;
type?: string;
node?: Node;
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;
@ -216,6 +272,14 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, {
this.recordInfo.startTime = null;
};
makeUnitRender = () => {
this.makeUnitRenderDebounced();
};
get autoRepaintNode() {
return container?.autoRepaintNode;
}
componentDidUpdate() {
this.recordTime();
}
@ -243,27 +307,6 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, {
};
}
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;
}
private curEventLeaf: Node | undefined;
setState(state: any) {
cache.state.set(componentCacheId, {
...this.state,
@ -274,23 +317,13 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, {
}
/** 由于内部属性变化,在触发渲染前,会执行该函数 */
beforeRender(type: string, node?: Node): void {
beforeRender(type: string, node?: INode): void {
this.recordInfo.startTime = Date.now();
this.recordInfo.type = type;
this.recordInfo.node = node;
setSchemaChangedSymbol?.(true);
}
renderUnitInfo: {
minimalUnitId?: string;
minimalUnitName?: string;
singleRender?: boolean;
};
get autoRepaintNode() {
return container.autoRepaintNode;
}
judgeMiniUnitRender() {
if (!this.renderUnitInfo) {
this.getRenderUnitInfo();
@ -308,7 +341,7 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, {
if (!ref) {
__debug('Cant find minimalRenderUnit ref! This make rerender!');
container.rerender();
container?.rerender();
return;
}
__debug(`${this.leaf?.componentName}(${this.props.componentId}) need render, make its minimalRenderUnit ${renderUnitInfo.minimalUnitName}(${renderUnitInfo.minimalUnitId})`);
@ -321,7 +354,7 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, {
return;
}
if (leaf.isRoot()) {
if (leaf.isRootNode) {
this.renderUnitInfo = {
singleRender: true,
...(this.renderUnitInfo || {}),
@ -347,32 +380,6 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, {
}
}
// 最小渲染单元做防抖处理
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);
makeUnitRender = () => {
this.makeUnitRenderDebounced();
};
componentWillReceiveProps(nextProps: any) {
let { componentId } = nextProps;
if (nextProps.__tag === this.props.__tag) {
@ -420,7 +427,7 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, {
// 目前多层循坏无法判断需要从哪一层开始渲染,故先粗暴解决
if (key === '___loop___') {
__debug('key is ___loop___, render a page!');
container.rerender();
container?.rerender();
// 由于 scope 变化,需要清空缓存,使用新的 scope
cache.component.delete(componentCacheId);
return;
@ -536,7 +543,7 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, {
return [];
}
get leaf(): Node | undefined {
get leaf(): INode | undefined {
if (this.props._leaf?.isMock) {
// 低代码组件作为一个整体更新,其内部的组件不需要监听相关事件
return undefined;
@ -570,13 +577,12 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, {
}
}
let LeafWrapper = forwardRef((props: any, ref: any) => (
// @ts-ignore
<LeafHoc
{...props}
forwardedRef={ref}
/>
));
let LeafWrapper = forwardRef((props: any, ref: any) => {
return createElement(LeafHoc, {
...props,
forwardedRef: ref,
});
});
LeafWrapper = cloneEnumerableProperty(LeafWrapper, Comp);

View File

@ -3,7 +3,7 @@
/* eslint-disable react/prop-types */
import classnames from 'classnames';
import { create as createDataSourceEngine } from '@alilc/lowcode-datasource-engine/interpret';
import { IPublicTypeNodeSchema, IPublicTypeNodeData, JSONValue, IPublicTypeCompositeValue } from '@alilc/lowcode-types';
import { IPublicTypeNodeSchema, IPublicTypeNodeData, IPublicTypeJSONValue, IPublicTypeCompositeValue } from '@alilc/lowcode-types';
import { isI18nData, isJSExpression, isJSFunction } from '@alilc/lowcode-utils';
import adapter from '../adapter';
import divFactory from '../components/Div';
@ -121,6 +121,8 @@ export default function baseRendererFactory(): IBaseRenderComponent {
let scopeIdx = 0;
return class BaseRenderer extends Component<IBaseRendererProps, Record<string, any>> {
[key: string]: any;
static displayName = 'BaseRenderer';
static defaultProps = {
@ -139,6 +141,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
__compScopes: Record<string, any> = {};
__instanceMap: Record<string, any> = {};
__dataHelper: any;
/**
* keep track of customMethods added to this context
*
@ -155,8 +158,6 @@ export default function baseRendererFactory(): IBaseRenderComponent {
*/
__styleElement: any;
[key: string]: any;
constructor(props: IBaseRendererProps, context: IBaseRendererContext) {
super(props, context);
this.context = context;
@ -242,6 +243,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
super.forceUpdate();
}
}
/**
* execute method in schema.lifeCycles
* @PRIVATE
@ -490,6 +492,10 @@ export default function baseRendererFactory(): IBaseRenderComponent {
}
const _children = getSchemaChildren(schema);
if (!schema.componentName) {
logger.error('The componentName in the schema is invalid, please check the schema: ', schema);
return;
}
// 解析占位组件
if (schema.componentName === 'Fragment' && _children) {
const tarChildren = isJSExpression(_children) ? this.__parseExpression(_children, scope) : _children;
@ -758,7 +764,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
const itemArg = (schema.loopArgs && schema.loopArgs[0]) || DEFAULT_LOOP_ARG_ITEM;
const indexArg = (schema.loopArgs && schema.loopArgs[1]) || DEFAULT_LOOP_ARG_INDEX;
const { loop } = schema;
return loop.map((item: JSONValue | IPublicTypeCompositeValue, i: number) => {
return loop.map((item: IPublicTypeJSONValue | IPublicTypeCompositeValue, i: number) => {
const loopSelf: any = {
[itemArg]: item,
[indexArg]: i,

View File

@ -6,6 +6,7 @@ import baseRendererFactory from './base';
import divFactory from '../components/Div';
import { IRenderComponent, IRendererProps, IRendererState } from '../types';
import { IPublicTypeNodeSchema, IPublicTypeRootSchema } from '@alilc/lowcode-types';
import logger from '../utils/logger';
export default function rendererFactory(): IRenderComponent {
const { PureComponent, Component, createElement, findDOMNode } = adapter.getRuntime();
@ -168,6 +169,7 @@ export default function rendererFactory(): IRenderComponent {
}
// 兼容乐高区块模板
if (schema.componentName !== 'Div' && !isFileSchema(schema)) {
logger.error('The root component name needs to be one of Page、Block、Component, please check the schema: ', schema);
return '模型结构异常';
}
debug('entry.render');

View File

@ -1,5 +1,5 @@
import type { ComponentLifecycle, CSSProperties } from 'react';
import { BuiltinSimulatorHost } from '@alilc/lowcode-designer';
import { BuiltinSimulatorHost, BuiltinSimulatorRenderer } from '@alilc/lowcode-designer';
import { RequestHandler, IPublicTypeNodeSchema, IPublicTypeRootSchema, IPublicTypeJSONObject } from '@alilc/lowcode-types';
export type ISchema = IPublicTypeNodeSchema | IPublicTypeRootSchema;
@ -8,16 +8,16 @@ export type ISchema = IPublicTypeNodeSchema | IPublicTypeRootSchema;
** Duck typed component type supporting both react and rax
*/
interface IGeneralComponent<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> {
readonly props: Readonly<P> & Readonly<{ children?: any | undefined }>;
state: Readonly<S>;
refs: Record<string, any>;
context: any;
setState<K extends keyof S>(
state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
callback?: () => void
): void;
forceUpdate(callback?: () => void): void;
render(): any;
readonly props: Readonly<P> & Readonly<{ children?: any | undefined }>;
state: Readonly<S>;
refs: Record<string, any>;
context: any;
}
export type IGeneralConstructor<
@ -60,20 +60,28 @@ export interface ILocationLike {
}
export type IRendererAppHelper = Partial<{
/** 全局公共函数 */
utils: Record<string, any>;
/** 全局常量 */
constants: Record<string, any>;
/** react-router 的 location 实例 */
location: ILocationLike;
/** react-router 的 history 实例 */
history: IHistoryLike;
/** @deprecated 已无业务使用 */
match: any;
/** @experimental 内部使用 */
logParams: Record<string, any>;
/** @experimental 内部使用 */
addons: Record<string, any>;
/** @experimental 内部使用 */
requestHandlersMap: Record<string, RequestHandler<{
data: unknown;
@ -86,25 +94,34 @@ export type IRendererAppHelper = Partial<{
* @see @todo @承虎
*/
export interface IRendererProps {
/** 符合低代码搭建协议的数据 */
schema: IPublicTypeRootSchema | IPublicTypeNodeSchema;
/** 组件依赖的实例 */
components: Record<string, IGeneralComponent>;
/** CSS 类名 */
className?: string;
/** style */
style?: CSSProperties;
/** id */
id?: string | number;
/** 语言 */
locale?: string;
/**
*
* https://lowcode-engine.cn/lowcode 中 2.6 国际化多语言支持
* */
messages?: Record<string, any>;
/** 主要用于设置渲染模块的全局上下文,里面定义的内容可以在低代码中通过 this 来访问,比如 this.utils */
appHelper?: IRendererAppHelper;
/**
* https://lowcode-engine.cn/lowcode
* 使
@ -112,33 +129,46 @@ export interface IRendererProps {
* >
*/
componentsMap?: { [key: string]: any };
/** 设计模式可选值live、design */
designMode?: string;
/** 渲染模块是否挂起,当设置为 true 时,渲染模块最外层容器的 shouldComponentUpdate 将始终返回false在下钻编辑或者多引擎渲染的场景会用到该参数。 */
suspended?: boolean;
/** 组件获取 ref 时触发的钩子 */
onCompGetRef?: (schema: IPublicTypeNodeSchema, ref: any) => void;
/** 组件 ctx 更新回调 */
onCompGetCtx?: (schema: IPublicTypeNodeSchema, ref: any) => void;
/** 传入的 schema 是否有变更 */
getSchemaChangedSymbol?: () => boolean;
/** 设置 schema 是否有变更 */
setSchemaChangedSymbol?: (symbol: boolean) => void;
/** 自定义创建 element 的钩子 */
customCreateElement?: (Component: any, props: any, children: any) => any;
/** 渲染类型,标识当前模块是以什么类型进行渲染的 */
rendererName?: 'LowCodeRenderer' | 'PageRenderer' | string;
/** 当找不到组件时,显示的组件 */
notFoundComponent?: IGeneralComponent;
/** 当组件渲染异常时,显示的组件 */
faultComponent?: IGeneralComponent;
/** 设备信息 */
device?: string;
/**
* @default true
* JSExpression 使 this 访
*/
thisRequiredInJSE?: boolean;
/**
* @default false
*
@ -162,7 +192,7 @@ export interface IBaseRendererProps {
__ctx: Record<string, any>;
__schema: IPublicTypeRootSchema;
__host?: BuiltinSimulatorHost;
__container?: any;
__container?: BuiltinSimulatorRenderer;
config?: Record<string, any>;
designMode?: 'design';
className?: string;
@ -173,6 +203,7 @@ export interface IBaseRendererProps {
thisRequiredInJSE?: boolean;
documentId?: string;
getNode?: any;
/**
* 'default'
*/
@ -213,13 +244,13 @@ export interface DataSource {
}
export interface IRuntime {
[key: string]: any;
Component: IGeneralConstructor;
PureComponent: IGeneralConstructor;
createElement: (...args: any) => any;
createContext: (...args: any) => any;
forwardRef: (...args: any) => any;
findDOMNode: (...args: any) => any;
[key: string]: any;
}
export interface IRendererModules {
@ -285,21 +316,22 @@ export interface IBaseRenderComponent {
}
export interface IRenderComponent {
displayName: string;
defaultProps: IRendererProps;
findDOMNode: (...args: any) => any;
new(props: IRendererProps, context: any): IGeneralComponent<IRendererProps, IRendererState> & {
[x: string]: any;
__getRef: (ref: any) => void;
componentDidMount(): Promise<void>;
componentDidUpdate(): Promise<void>;
componentWillUnmount(): Promise<void>;
componentDidCatch(e: any): Promise<void>;
shouldComponentUpdate(nextProps: IRendererProps): boolean;
__getRef: (ref: any) => void;
isValidComponent(SetComponent: any): any;
patchDidCatch(SetComponent: any): void;
createElement(SetComponent: any, props: any, children?: any): any;
getNotFoundComponent(): any;
getFaultComponent(): any;
};
displayName: string;
defaultProps: IRendererProps;
findDOMNode: (...args: any) => any;
}

View File

@ -324,6 +324,7 @@ describe('mini unit render', () => {
it('change component leaf isRoot is true', () => {
const TextNode = new Node(textSchema, {
isRoot: true,
isRootNode: true,
});
nodeMap.set(textSchema.id, TextNode);
@ -356,6 +357,7 @@ describe('mini unit render', () => {
id: 'rootId',
}, {
isRoot: true,
isRootNode: true
}),
})
});

View File

@ -1,5 +1,5 @@
import React, { Component, createElement, PureComponent, createContext } from 'react';
import React, { Component, createElement, forwardRef, PureComponent, createContext } from 'react';
const mockGetRenderers = jest.fn();
const mockGetRuntime = jest.fn();
const mockParseExpression = jest.fn();
@ -59,6 +59,7 @@ describe('Base Render methods', () => {
createElement,
PureComponent,
createContext,
forwardRef,
});
RendererClass = baseRendererFactory();
})

View File

@ -40,6 +40,10 @@ export default class Node {
isRoot = () => this._isRoot;
get isRootNode () {
return this._isRoot;
};
// componentMeta() {
// return this.componentMeta;
// }