packages/renderer-core/src/renderer/base.tsx (857 lines of code) (raw):
/* eslint-disable no-console */
/* eslint-disable max-len */
/* eslint-disable react/prop-types */
import classnames from 'classnames';
import { create as createDataSourceEngine } from '@alilc/lowcode-datasource-engine/interpret';
import { IPublicTypeNodeSchema, IPublicTypeNodeData, IPublicTypeJSONValue, IPublicTypeCompositeValue } from '@alilc/lowcode-types';
import { checkPropTypes, isI18nData, isJSExpression, isJSFunction } from '@alilc/lowcode-utils';
import adapter from '../adapter';
import divFactory from '../components/Div';
import visualDomFactory from '../components/VisualDom';
import contextFactory from '../context';
import {
forEach,
getValue,
parseData,
parseExpression,
parseThisRequiredExpression,
parseI18n,
isEmpty,
isSchema,
isFileSchema,
transformArrayToMap,
transformStringToFunction,
getI18n,
getFileCssName,
capitalizeFirstLetter,
DataHelper,
isVariable,
isJSSlot,
} from '../utils';
import { IBaseRendererProps, INodeInfo, IBaseRenderComponent, IBaseRendererContext, IRendererAppHelper, DataSource } from '../types';
import { compWrapper } from '../hoc';
import { IComponentConstruct, leafWrapper } from '../hoc/leaf';
import logger from '../utils/logger';
import isUseLoop from '../utils/is-use-loop';
/**
* execute method in schema.lifeCycles with context
* @PRIVATE
*/
export function executeLifeCycleMethod(context: any, schema: IPublicTypeNodeSchema, method: string, args: any, thisRequiredInJSE: boolean | undefined): any {
if (!context || !isSchema(schema) || !method) {
return;
}
const lifeCycleMethods = getValue(schema, 'lifeCycles', {});
let fn = lifeCycleMethods[method];
if (!fn) {
return;
}
// TODO: cache
if (isJSExpression(fn) || isJSFunction(fn)) {
fn = thisRequiredInJSE ? parseThisRequiredExpression(fn, context) : parseExpression(fn, context);
}
if (typeof fn !== 'function') {
logger.error(`生命周期${method}类型不符`, fn);
return;
}
try {
return fn.apply(context, args);
} catch (e) {
logger.error(`[${schema.componentName}]生命周期${method}出错`, e);
}
}
/**
* get children from a node schema
* @PRIVATE
*/
export function getSchemaChildren(schema: IPublicTypeNodeSchema | undefined) {
if (!schema) {
return;
}
if (!schema.props) {
return schema.children;
}
if (!schema.children) {
return schema.props.children;
}
if (!schema.props.children) {
return schema.children;
}
let result = ([] as IPublicTypeNodeData[]).concat(schema.children);
if (Array.isArray(schema.props.children)) {
result = result.concat(schema.props.children);
} else {
result.push(schema.props.children);
}
return result;
}
export default function baseRendererFactory(): IBaseRenderComponent {
const { BaseRenderer: customBaseRenderer } = adapter.getRenderers();
if (customBaseRenderer) {
return customBaseRenderer;
}
const { Component, createElement } = adapter.getRuntime();
const Div = divFactory();
const VisualDom = visualDomFactory();
const AppContext = contextFactory();
const DESIGN_MODE = {
EXTEND: 'extend',
BORDER: 'border',
PREVIEW: 'preview',
};
const OVERLAY_LIST = ['Dialog', 'Overlay', 'Animate', 'ConfigProvider'];
const DEFAULT_LOOP_ARG_ITEM = 'item';
const DEFAULT_LOOP_ARG_INDEX = 'index';
let scopeIdx = 0;
return class BaseRenderer extends Component<IBaseRendererProps, Record<string, any>> {
[key: string]: any;
static displayName = 'BaseRenderer';
static defaultProps = {
__schema: {},
};
static contextType = AppContext;
i18n: any;
getLocale: any;
setLocale: any;
dataSourceMap: Record<string, any> = {};
__namespace = 'base';
__compScopes: Record<string, any> = {};
__instanceMap: Record<string, any> = {};
__dataHelper: any;
/**
* keep track of customMethods added to this context
*
* @type {any}
*/
__customMethodsList: any[] = [];
__parseExpression: any;
__ref: any;
/**
* reference of style element contains schema.css
*
* @type {any}
*/
__styleElement: any;
constructor(props: IBaseRendererProps, context: IBaseRendererContext) {
super(props, context);
this.context = context;
this.__parseExpression = (str: string, self: any) => {
return parseExpression({ str, self, thisRequired: props?.thisRequiredInJSE, logScope: props.componentName });
};
this.__beforeInit(props);
this.__init(props);
this.__afterInit(props);
this.__debug(`constructor - ${props?.__schema?.fileName}`);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
__beforeInit(_props: IBaseRendererProps) { }
__init(props: IBaseRendererProps) {
this.__compScopes = {};
this.__instanceMap = {};
this.__bindCustomMethods(props);
this.__initI18nAPIs();
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
__afterInit(_props: IBaseRendererProps) { }
static getDerivedStateFromProps(props: IBaseRendererProps, state: any) {
const result = executeLifeCycleMethod(this, props?.__schema, 'getDerivedStateFromProps', [props, state], props.thisRequiredInJSE);
return result === undefined ? null : result;
}
async getSnapshotBeforeUpdate(...args: any[]) {
this.__executeLifeCycleMethod('getSnapshotBeforeUpdate', args);
this.__debug(`getSnapshotBeforeUpdate - ${this.props?.__schema?.fileName}`);
}
async componentDidMount(...args: any[]) {
this.reloadDataSource();
this.__executeLifeCycleMethod('componentDidMount', args);
this.__debug(`componentDidMount - ${this.props?.__schema?.fileName}`);
}
async componentDidUpdate(...args: any[]) {
this.__executeLifeCycleMethod('componentDidUpdate', args);
this.__debug(`componentDidUpdate - ${this.props.__schema.fileName}`);
}
async componentWillUnmount(...args: any[]) {
this.__executeLifeCycleMethod('componentWillUnmount', args);
this.__debug(`componentWillUnmount - ${this.props?.__schema?.fileName}`);
}
async componentDidCatch(...args: any[]) {
this.__executeLifeCycleMethod('componentDidCatch', args);
logger.warn(args);
}
reloadDataSource = () => new Promise((resolve, reject) => {
this.__debug('reload data source');
if (!this.__dataHelper) {
return resolve({});
}
this.__dataHelper.getInitData()
.then((res: any) => {
if (isEmpty(res)) {
this.forceUpdate();
return resolve({});
}
this.setState(res, resolve as () => void);
})
.catch((err: Error) => {
reject(err);
});
});
shouldComponentUpdate() {
if (this.props.getSchemaChangedSymbol?.() && this.props.__container?.rerender) {
this.props.__container?.rerender();
return false;
}
return true;
}
forceUpdate() {
if (this.shouldComponentUpdate()) {
super.forceUpdate();
}
}
/**
* execute method in schema.lifeCycles
* @PRIVATE
*/
__executeLifeCycleMethod = (method: string, args?: any) => {
executeLifeCycleMethod(this, this.props.__schema, method, args, this.props.thisRequiredInJSE);
};
/**
* this method is for legacy purpose only, which used _ prefix instead of __ as private for some historical reasons
* @LEGACY
*/
_getComponentView = (componentName: string) => {
const { __components } = this.props;
if (!__components) {
return;
}
return __components[componentName];
};
__bindCustomMethods = (props: IBaseRendererProps) => {
const { __schema } = props;
const customMethodsList = Object.keys(__schema.methods || {}) || [];
(this.__customMethodsList || []).forEach((item: any) => {
if (!customMethodsList.includes(item)) {
delete this[item];
}
});
this.__customMethodsList = customMethodsList;
forEach(__schema.methods, (val: any, key: string) => {
let value = val;
if (isJSExpression(value) || isJSFunction(value)) {
value = this.__parseExpression(value, this);
}
if (typeof value !== 'function') {
logger.error(`custom method ${key} can not be parsed to a valid function`, value);
return;
}
this[key] = value.bind(this);
});
};
__generateCtx = (ctx: Record<string, any>) => {
const { pageContext, compContext } = this.context;
const obj = {
page: pageContext,
component: compContext,
...ctx,
};
forEach(obj, (val: any, key: string) => {
this[key] = val;
});
};
__parseData = (data: any, ctx?: Record<string, any>) => {
const { __ctx, thisRequiredInJSE, componentName } = this.props;
return parseData(data, ctx || __ctx || this, { thisRequiredInJSE, logScope: componentName });
};
__initDataSource = (props: IBaseRendererProps) => {
if (!props) {
return;
}
const schema = props.__schema || {};
const defaultDataSource: DataSource = {
list: [],
};
const dataSource = schema.dataSource || defaultDataSource;
// requestHandlersMap 存在才走数据源引擎方案
// TODO: 下面if else 抽成独立函数
const useDataSourceEngine = !!(props.__appHelper?.requestHandlersMap);
if (useDataSourceEngine) {
this.__dataHelper = {
updateConfig: (updateDataSource: any) => {
const { dataSourceMap, reloadDataSource } = createDataSourceEngine(
updateDataSource ?? {},
this,
props.__appHelper.requestHandlersMap ? { requestHandlersMap: props.__appHelper.requestHandlersMap } : undefined,
);
this.reloadDataSource = () => new Promise((resolve) => {
this.__debug('reload data source');
reloadDataSource().then(() => {
resolve({});
});
});
return dataSourceMap;
},
};
this.dataSourceMap = this.__dataHelper.updateConfig(dataSource);
} else {
const appHelper = props.__appHelper;
this.__dataHelper = new DataHelper(this, dataSource, appHelper, (config: any) => this.__parseData(config));
this.dataSourceMap = this.__dataHelper.dataSourceMap;
this.reloadDataSource = () => new Promise((resolve, reject) => {
this.__debug('reload data source');
if (!this.__dataHelper) {
return resolve({});
}
this.__dataHelper.getInitData()
.then((res: any) => {
if (isEmpty(res)) {
return resolve({});
}
this.setState(res, resolve as () => void);
})
.catch((err: Error) => {
reject(err);
});
});
}
};
/**
* init i18n apis
* @PRIVATE
*/
__initI18nAPIs = () => {
this.i18n = (key: string, values = {}) => {
const { locale, messages } = this.props;
return getI18n(key, values, locale, messages);
};
this.getLocale = () => this.props.locale;
this.setLocale = (loc: string) => {
const setLocaleFn = this.appHelper?.utils?.i18n?.setLocale;
if (!setLocaleFn || typeof setLocaleFn !== 'function') {
logger.warn('initI18nAPIs Failed, i18n only works when appHelper.utils.i18n.setLocale() exists');
return undefined;
}
return setLocaleFn(loc);
};
};
/**
* write props.__schema.css to document as a style element,
* which will be added once and only once.
* @PRIVATE
*/
__writeCss = (props: IBaseRendererProps) => {
const css = getValue(props.__schema, 'css', '');
this.__debug('create this.styleElement with css', css);
let style = this.__styleElement;
if (!this.__styleElement) {
style = document.createElement('style');
style.type = 'text/css';
style.setAttribute('from', 'style-sheet');
const head = document.head || document.getElementsByTagName('head')[0];
head.appendChild(style);
this.__styleElement = style;
this.__debug('this.styleElement is created', this.__styleElement);
}
if (style.innerHTML === css) {
return;
}
style.innerHTML = css;
};
__render = () => {
const schema = this.props.__schema;
this.__executeLifeCycleMethod('render');
this.__writeCss(this.props);
const { engine } = this.context;
if (engine) {
engine.props.onCompGetCtx(schema, this);
// 画布场景才需要每次渲染bind自定义方法
if (this.__designModeIsDesign) {
this.__bindCustomMethods(this.props);
this.dataSourceMap = this.__dataHelper?.updateConfig(schema.dataSource);
}
}
};
__getRef = (ref: any) => {
const { engine } = this.context;
const { __schema } = this.props;
ref && engine?.props?.onCompGetRef(__schema, ref);
this.__ref = ref;
};
__createDom = () => {
const { __schema, __ctx, __components = {} } = this.props;
// merge defaultProps
const scopeProps = {
...__schema.defaultProps,
...this.props,
};
const scope: any = {
props: scopeProps,
};
scope.__proto__ = __ctx || this;
const _children = getSchemaChildren(__schema);
let Comp = __components[__schema.componentName];
if (!Comp) {
this.__debug(`${__schema.componentName} is invalid!`);
}
const parentNodeInfo = ({
schema: __schema,
Comp: this.__getHOCWrappedComponent(Comp, __schema, scope),
} as INodeInfo);
return this.__createVirtualDom(_children, scope, parentNodeInfo);
};
/**
* 将模型结构转换成react Element
* @param originalSchema schema
* @param originalScope scope
* @param parentInfo 父组件的信息,包含schema和Comp
* @param idx 为循环渲染的循环Index
*/
__createVirtualDom = (originalSchema: IPublicTypeNodeData | IPublicTypeNodeData[] | undefined, originalScope: any, parentInfo: INodeInfo, idx: string | number = ''): any => {
if (originalSchema === null || originalSchema === undefined) {
return null;
}
let scope = originalScope;
let schema = originalSchema;
const { engine } = this.context || {};
if (!engine) {
this.__debug('this.context.engine is invalid!');
return null;
}
try {
const { __appHelper: appHelper, __components: components = {} } = this.props || {};
if (isJSExpression(schema)) {
return this.__parseExpression(schema, scope);
}
if (isI18nData(schema)) {
return parseI18n(schema, scope);
}
if (isJSSlot(schema)) {
return this.__createVirtualDom(schema.value, scope, parentInfo);
}
if (typeof schema === 'string') {
return schema;
}
if (typeof schema === 'number' || typeof schema === 'boolean') {
return String(schema);
}
if (Array.isArray(schema)) {
if (schema.length === 1) {
return this.__createVirtualDom(schema[0], scope, parentInfo);
}
return schema.map((item, idy) => this.__createVirtualDom(item, scope, parentInfo, (item as IPublicTypeNodeSchema)?.__ctx?.lceKey ? '' : String(idy)));
}
// @ts-expect-error 如果直接转换好了,可以返回
if (schema.$$typeof) {
return schema;
}
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;
return this.__createVirtualDom(tarChildren, scope, parentInfo);
}
if (schema.componentName === 'Text' && typeof schema.props?.text === 'string') {
const text: string = schema.props?.text;
schema = { ...schema };
schema.children = [text];
}
if (!isSchema(schema)) {
return null;
}
let Comp = components[schema.componentName] || this.props.__container?.components?.[schema.componentName];
// 容器类组件的上下文通过props传递,避免context传递带来的嵌套问题
const otherProps: any = isFileSchema(schema)
? {
__schema: schema,
__appHelper: appHelper,
__components: components,
}
: {};
if (!Comp) {
logger.error(`${schema.componentName} component is not found in components list! component list is:`, components || this.props.__container?.components);
return engine.createElement(
engine.getNotFoundComponent(),
{
componentName: schema.componentName,
componentId: schema.id,
enableStrictNotFoundMode: engine.props.enableStrictNotFoundMode,
ref: (ref: any) => {
ref && engine.props?.onCompGetRef(schema, ref);
},
},
this.__getSchemaChildrenVirtualDom(schema, scope, Comp),
);
}
if (schema.loop != null) {
const loop = this.__parseData(schema.loop, scope);
if (Array.isArray(loop) && loop.length === 0) return null;
const useLoop = isUseLoop(loop, this.__designModeIsDesign);
if (useLoop) {
return this.__createLoopVirtualDom(
{
...schema,
loop,
},
scope,
parentInfo,
idx,
);
}
}
const condition = schema.condition == null ? true : this.__parseData(schema.condition, scope);
// DesignMode 为 design 情况下,需要进入 leaf Hoc,进行相关事件注册
const displayInHook = this.__designModeIsDesign;
if (!condition && !displayInHook) {
return null;
}
let scopeKey = '';
// 判断组件是否需要生成scope,且只生成一次,挂在this.__compScopes上
if (Comp.generateScope) {
const key = this.__parseExpression(schema.props?.key, scope);
if (key) {
// 如果组件自己设置key则使用组件自己的key
scopeKey = key;
} else if (!schema.__ctx) {
// 在生产环境schema没有__ctx上下文,需要手动生成一个lceKey
schema.__ctx = {
lceKey: `lce${++scopeIdx}`,
};
scopeKey = schema.__ctx.lceKey;
} else {
// 需要判断循环的情况
scopeKey = schema.__ctx.lceKey + (idx !== undefined ? `_${idx}` : '');
}
if (!this.__compScopes[scopeKey]) {
this.__compScopes[scopeKey] = Comp.generateScope(this, schema);
}
}
// 如果组件有设置scope,需要为组件生成一个新的scope上下文
if (scopeKey && this.__compScopes[scopeKey]) {
const compSelf = { ...this.__compScopes[scopeKey] };
compSelf.__proto__ = scope;
scope = compSelf;
}
if (engine.props?.designMode) {
otherProps.__designMode = engine.props.designMode;
}
if (this.__designModeIsDesign) {
otherProps.__tag = Math.random();
}
const componentInfo: any = {};
const props: any = this.__getComponentProps(schema, scope, Comp, {
...componentInfo,
props: transformArrayToMap(componentInfo.props, 'name'),
}) || {};
this.__componentHOCs.forEach((ComponentConstruct: IComponentConstruct) => {
Comp = ComponentConstruct(Comp, {
schema,
componentInfo,
baseRenderer: this,
scope,
});
});
otherProps.ref = (ref: any) => {
this.$(props.fieldId || props.ref, ref); // 收集ref
const refProps = props.ref;
if (refProps && typeof refProps === 'string') {
this[refProps] = ref;
}
ref && engine.props?.onCompGetRef(schema, ref);
};
// scope需要传入到组件上
if (scopeKey && this.__compScopes[scopeKey]) {
props.__scope = this.__compScopes[scopeKey];
}
if (schema?.__ctx?.lceKey) {
if (!isFileSchema(schema)) {
engine.props?.onCompGetCtx(schema, scope);
}
props.key = props.key || `${schema.__ctx.lceKey}_${schema.__ctx.idx || 0}_${idx !== undefined ? idx : ''}`;
} else if ((typeof idx === 'number' || typeof idx === 'string') && !props.key) {
// 仅当循环场景走这里
props.key = idx;
}
props.__id = schema.id;
if (!props.key) {
props.key = props.__id;
}
let child = this.__getSchemaChildrenVirtualDom(schema, scope, Comp, condition);
const renderComp = (innerProps: any) => engine.createElement(Comp, innerProps, child);
// 设计模式下的特殊处理
if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) {
// 对于overlay,dialog等组件为了使其在设计模式下显示,外层需要增加一个div容器
if (OVERLAY_LIST.includes(schema.componentName)) {
const { ref, ...overlayProps } = otherProps;
return createElement(Div, {
ref,
__designMode: engine.props.designMode,
}, renderComp({ ...props, ...overlayProps }));
}
// 虚拟dom显示
if (componentInfo?.parentRule) {
const parentList = componentInfo.parentRule.split(',');
const { schema: parentSchema = { componentName: '' }, Comp: parentComp } = parentInfo;
if (
!parentList.includes(parentSchema.componentName) ||
parentComp !== components[parentSchema.componentName]
) {
props.__componentName = schema.componentName;
Comp = VisualDom;
} else {
// 若虚拟dom在正常的渲染上下文中,就不显示设计模式了
props.__disableDesignMode = true;
}
}
}
return renderComp({
...props,
...otherProps,
__inner__: {
hidden: schema.hidden,
condition,
},
});
} catch (e) {
return engine.createElement(engine.getFaultComponent(), {
error: e,
schema,
self: scope,
parentInfo,
idx,
});
}
};
/**
* get Component HOCs
*
* @readonly
* @type {IComponentConstruct[]}
*/
get __componentHOCs(): IComponentConstruct[] {
if (this.__designModeIsDesign) {
return [leafWrapper, compWrapper];
}
return [compWrapper];
}
__getSchemaChildrenVirtualDom = (schema: IPublicTypeNodeSchema | undefined, scope: any, Comp: any, condition = true) => {
let children = condition ? getSchemaChildren(schema) : null;
// @todo 补完这里的 Element 定义 @承虎
let result: any = [];
if (children) {
if (!Array.isArray(children)) {
children = [children];
}
children.forEach((child: any) => {
const childVirtualDom = this.__createVirtualDom(
isJSExpression(child) ? this.__parseExpression(child, scope) : child,
scope,
{
schema,
Comp,
},
);
result.push(childVirtualDom);
});
}
if (result && result.length > 0) {
return result;
}
return null;
};
__getComponentProps = (schema: IPublicTypeNodeSchema | undefined, scope: any, Comp: any, componentInfo?: any) => {
if (!schema) {
return {};
}
return this.__parseProps(schema?.props, scope, '', {
schema,
Comp,
componentInfo: {
...(componentInfo || {}),
props: transformArrayToMap((componentInfo || {}).props, 'name'),
},
}) || {};
};
__createLoopVirtualDom = (schema: IPublicTypeNodeSchema, scope: any, parentInfo: INodeInfo, idx: number | string) => {
if (isFileSchema(schema)) {
logger.warn('file type not support Loop');
return null;
}
if (!Array.isArray(schema.loop)) {
return null;
}
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: IPublicTypeJSONValue | IPublicTypeCompositeValue, i: number) => {
const loopSelf: any = {
[itemArg]: item,
[indexArg]: i,
};
loopSelf.__proto__ = scope;
return this.__createVirtualDom(
{
...schema,
loop: undefined,
props: {
...schema.props,
// 循环下 key 不能为常量,这样会造成 key 值重复,渲染异常
key: isJSExpression(schema.props?.key) ? schema.props?.key : null,
},
},
loopSelf,
parentInfo,
idx ? `${idx}_${i}` : i,
);
});
};
get __designModeIsDesign() {
const { engine } = this.context || {};
return engine?.props?.designMode === 'design';
}
__parseProps = (originalProps: any, scope: any, path: string, info: INodeInfo): any => {
let props = originalProps;
const { schema, Comp, componentInfo = {} } = info;
const propInfo = getValue(componentInfo.props, path);
// FIXME: 将这行逻辑外置,解耦,线上环境不要验证参数,调试环境可以有,通过传参自定义
const propType = propInfo?.extra?.propType;
const checkProps = (value: any) => {
if (!propType) {
return value;
}
return checkPropTypes(value, path, propType, componentInfo.name) ? value : undefined;
};
const parseReactNode = (data: any, params: any) => {
if (isEmpty(params)) {
const virtualDom = this.__createVirtualDom(data, scope, ({ schema, Comp } as INodeInfo));
return checkProps(virtualDom);
}
return checkProps((...argValues: any[]) => {
const args: any = {};
if (Array.isArray(params) && params.length) {
params.forEach((item, idx) => {
if (typeof item === 'string') {
args[item] = argValues[idx];
} else if (item && typeof item === 'object') {
args[item.name] = argValues[idx];
}
});
}
args.__proto__ = scope;
return scope.__createVirtualDom(data, args, ({ schema, Comp } as INodeInfo));
});
};
if (isJSExpression(props)) {
props = this.__parseExpression(props, scope);
// 只有当变量解析出来为模型结构的时候才会继续解析
if (!isSchema(props) && !isJSSlot(props)) {
return checkProps(props);
}
}
const handleI18nData = (innerProps: any) => innerProps[innerProps.use || (this.getLocale && this.getLocale()) || 'zh-CN'];
// @LEGACY 兼容老平台设计态 i18n 数据
if (isI18nData(props)) {
const i18nProp = handleI18nData(props);
if (i18nProp) {
props = i18nProp;
} else {
return parseI18n(props, scope);
}
}
// @LEGACY 兼容老平台设计态的变量绑定
if (isVariable(props)) {
props = props.value;
if (isI18nData(props)) {
props = handleI18nData(props);
}
}
if (isJSFunction(props)) {
props = transformStringToFunction(props.value);
}
if (isJSSlot(props)) {
const { params, value } = props;
if (!isSchema(value) || isEmpty(value)) {
return undefined;
}
return parseReactNode(value, params);
}
// 兼容通过componentInfo判断的情况
if (isSchema(props)) {
const isReactNodeFunction = !!(propInfo?.type === 'ReactNode' && propInfo?.props?.type === 'function');
const isMixinReactNodeFunction = !!(
propInfo?.type === 'Mixin'
&& propInfo?.props?.types?.indexOf('ReactNode') > -1
&& propInfo?.props?.reactNodeProps?.type === 'function'
);
let params = null;
if (isReactNodeFunction) {
params = propInfo?.props?.params;
} else if (isMixinReactNodeFunction) {
params = propInfo?.props?.reactNodeProps?.params;
}
return parseReactNode(
props,
params,
);
}
if (Array.isArray(props)) {
return checkProps(props.map((item, idx) => this.__parseProps(item, scope, path ? `${path}.${idx}` : `${idx}`, info)));
}
if (typeof props === 'function') {
return checkProps(props.bind(scope));
}
if (props && typeof props === 'object') {
if (props.$$typeof) {
return checkProps(props);
}
const res: any = {};
forEach(props, (val: any, key: string) => {
if (key.startsWith('__')) {
res[key] = val;
return;
}
res[key] = this.__parseProps(val, scope, path ? `${path}.${key}` : key, info);
});
return checkProps(res);
}
return checkProps(props);
};
$(filedId: string, instance?: any) {
this.__instanceMap = this.__instanceMap || {};
if (!filedId || typeof filedId !== 'string') {
return this.__instanceMap;
}
if (instance) {
this.__instanceMap[filedId] = instance;
}
return this.__instanceMap[filedId];
}
__debug = (...args: any[]) => { logger.debug(...args); };
__renderContextProvider = (customProps?: object, children?: any) => {
return createElement(AppContext.Provider, {
value: {
...this.context,
blockContext: this,
...(customProps || {}),
},
children: children || this.__createDom(),
});
};
__renderContextConsumer = (children: any) => {
return createElement(AppContext.Consumer, {}, children);
};
__getHOCWrappedComponent(OriginalComp: any, schema: any, scope: any) {
let Comp = OriginalComp;
this.__componentHOCs.forEach((ComponentConstruct: IComponentConstruct) => {
Comp = ComponentConstruct(Comp || Div, {
schema,
componentInfo: {},
baseRenderer: this,
scope,
});
});
return Comp;
}
__renderComp(OriginalComp: any, ctxProps: object) {
let Comp = OriginalComp;
const { __schema, __ctx } = this.props;
const scope: any = {};
scope.__proto__ = __ctx || this;
Comp = this.__getHOCWrappedComponent(Comp, __schema, scope);
const data = this.__parseProps(__schema?.props, scope, '', {
schema: __schema,
Comp,
componentInfo: {},
});
const { className } = data;
const otherProps: any = {};
const { engine } = this.context || {};
if (!engine) {
return null;
}
if (this.__designModeIsDesign) {
otherProps.__tag = Math.random();
}
const child = engine.createElement(
Comp,
{
...data,
...this.props,
ref: this.__getRef,
className: classnames(getFileCssName(__schema?.fileName), className, this.props.className),
__id: __schema?.id,
...otherProps,
},
this.__createDom(),
);
return this.__renderContextProvider(ctxProps, child);
}
__renderContent(children: any) {
const { __schema } = this.props;
const parsedProps = this.__parseData(__schema.props);
const className = classnames(`lce-${this.__namespace}`, getFileCssName(__schema.fileName), parsedProps.className, this.props.className);
const style = { ...(parsedProps.style || {}), ...(typeof this.props.style === 'object' ? this.props.style : {}) };
const id = this.props.id || parsedProps.id;
return createElement('div', {
ref: this.__getRef,
className,
id,
style,
}, children);
}
__checkSchema = (schema: IPublicTypeNodeSchema | undefined, originalExtraComponents: string | string[] = []) => {
let extraComponents = originalExtraComponents;
if (typeof extraComponents === 'string') {
extraComponents = [extraComponents];
}
const builtin = capitalizeFirstLetter(this.__namespace);
const componentNames = [builtin, ...extraComponents];
return !isSchema(schema) || !componentNames.includes(schema?.componentName ?? '');
};
get appHelper(): IRendererAppHelper {
return this.props.__appHelper;
}
get requestHandlersMap() {
return this.appHelper?.requestHandlersMap;
}
get utils() {
return this.appHelper?.utils;
}
get constants() {
return this.appHelper?.constants;
}
get history() {
return this.appHelper?.history;
}
get location() {
return this.appHelper?.location;
}
get match() {
return this.appHelper?.match;
}
render() {
return null;
}
};
}