2020-08-04 12:35:24 +08:00

554 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Debug from 'debug';
import Div from '../components/Div';
import VisualDom from '../components/VisualDom';
import AppContext from '../context/appContext';
import DataHelper from '../utils/dataHelper';
import {
forEach,
getValue,
parseData,
parseExpression,
isEmpty,
isSchema,
isFileSchema,
isJSExpression,
isJSSlot,
isJSFunction,
transformArrayToMap,
transformStringToFunction,
checkPropTypes,
generateI18n,
acceptsRef,
} from '../utils';
const debug = Debug('renderer:base');
const DESIGN_MODE = {
EXTEND: 'extend',
BORDER: 'border',
PREVIEW: 'preview',
};
const OVERLAY_LIST = ['Dialog', 'Overlay', 'Animate', 'ConfigProvider'];
let scopeIdx = 0;
export default class BaseRender extends PureComponent {
static dislayName = 'base-renderer';
static propTypes = {
locale: PropTypes.string,
messages: PropTypes.object,
__appHelper: PropTypes.object,
__components: PropTypes.object,
__ctx: PropTypes.object,
__schema: PropTypes.object,
};
static defaultProps = {
__schema: {},
};
static contextType = AppContext;
constructor(props, context) {
super(props, context);
this.appHelper = props.__appHelper;
this.__compScopes = {};
const { locale, messages } = props;
this.i18n = generateI18n(locale, messages);
this.__bindCustomMethods(props);
}
async getSnapshotBeforeUpdate() {
this.__setLifeCycleMethods('getSnapshotBeforeUpdate', arguments);
}
async componentDidMount() {
this.reloadDataSource();
this.__setLifeCycleMethods('componentDidMount', arguments);
}
async componentDidUpdate() {
this.__setLifeCycleMethods('componentDidUpdate', arguments);
}
async componentWillUnmount() {
this.__setLifeCycleMethods('componentWillUnmount', arguments);
}
async componentDidCatch(e) {
this.__setLifeCycleMethods('componentDidCatch', arguments);
console.warn(e);
}
reloadDataSource = () => {
return new Promise((resolve, reject) => {
debug('reload data source');
if (!this.__dataHelper) {
this.__showPlaceholder = false;
return resolve();
}
this.__dataHelper
.getInitData()
.then((res) => {
this.__showPlaceholder = false;
if (isEmpty(res)) {
this.forceUpdate();
return resolve();
}
this.setState(res, resolve);
})
.catch((err) => {
if (this.__showPlaceholder) {
this.__showPlaceholder = false;
this.forceUpdate();
}
reject(err);
});
});
};
__setLifeCycleMethods = (method, args) => {
const lifeCycleMethods = getValue(this.props.__schema, 'lifeCycles', {});
let fn = lifeCycleMethods[method];
if (fn) {
// TODO, cache
if (isJSExpression(fn) || isJSFunction(fn)) {
fn = parseExpression(fn, this);
}
if (typeof fn !== 'function') {
console.error(`生命周期${method}类型不符`, fn);
return;
}
try {
return fn.apply(this, args);
} catch (e) {
console.error(`[${this.props.__schema.componentName}]生命周期${method}出错`, e);
}
}
};
__bindCustomMethods = (props = this.props) => {
const { __schema } = props;
const customMethodsList = Object.keys(__schema.methods || {}) || [];
this.__customMethodsList &&
this.__customMethodsList.forEach((item) => {
if (!customMethodsList.includes(item)) {
delete this[item];
}
});
this.__customMethodsList = customMethodsList;
forEach(__schema.methods, (val, key) => {
if (isJSExpression(val) || isJSFunction(val)) {
val = parseExpression(val, this);
}
if (typeof val !== 'function') {
console.error(`自定义函数${key}类型不符`, val);
return;
}
this[key] = val.bind(this);
});
};
__generateCtx = (ctx) => {
const { pageContext, compContext } = this.context;
const obj = {
page: pageContext,
component: compContext,
...ctx,
};
forEach(obj, (val, key) => {
this[key] = val;
});
};
__parseData = (data, ctx) => {
const { __ctx } = this.props;
return parseData(data, ctx || __ctx || this);
};
__initDataSource = (props = this.props) => {
const schema = props.__schema || {};
const appHelper = props.__appHelper;
const dataSource = (schema && schema.dataSource) || {};
this.__dataHelper = new DataHelper(this, dataSource, appHelper, (config) => this.__parseData(config));
this.dataSourceMap = this.__dataHelper.dataSourceMap;
// 设置容器组件占位若设置占位则在初始异步请求完成之前用loading占位且不渲染容器组件内部内容
this.__showPlaceholder =
this.__parseData(schema.props && schema.props.autoLoading) &&
(dataSource.list || []).some((item) => !!this.__parseData(item.isInit));
};
__render = () => {
const schema = this.props.__schema;
this.__setLifeCycleMethods('render');
const engine = this.context.engine;
if (engine) {
engine.props.onCompGetCtx(schema, this);
// 画布场景才需要每次渲染bind自定义方法
if (engine.props.designMode) {
this.__bindCustomMethods();
this.dataSourceMap = this.__dataHelper && this.__dataHelper.updateConfig(schema.dataSource);
}
}
};
__getRef = (ref) => {
this.__ref = ref;
};
getSchemaChildren = (schema) => {
if (!schema || !schema.props) {
return schema?.children;
}
let _children = schema.children;
if (!_children) return schema.props.children;
if (schema.props.children && schema.props.children.length) {
if (Array.isArray(schema.props.children)) {
_children = Array.isArray(_children) ? _children.concat(schema.props.children) : schema.props.children.unshift(_children);
} else {
Array.isArray(_children) && _children.push(schema.props.children) || (_children = [_children] && _children.push(schema.props.children));
}
}
return _children;
};
__createDom = () => {
const { __schema, __ctx, __components = {} } = this.props;
const self = {};
self.__proto__ = __ctx || this;
let _children = this.getSchemaChildren(__schema);
return this.__createVirtualDom(_children, self, {
schema: __schema,
Comp: __components[__schema.componentName],
});
};
// 将模型结构转换成react Element
// schema 模型结构
// self 为每个渲染组件构造的上下文self是自上而下继承的
// parentInfo 父组件的信息包含schema和Comp
// idx 若为循环渲染的循环Index
__createVirtualDom = (schema, self, parentInfo, idx) => {
const { engine } = this.context || {};
try {
if (!schema) return null;
const { __appHelper: appHelper, __components: components = {} } =
this.props || {};
if (isJSExpression(schema)) {
return parseExpression(schema, self);
}
if (typeof schema === 'string') return schema;
if (typeof schema === 'number' || typeof schema === 'boolean') {
return schema.toString();
}
if (Array.isArray(schema)) {
if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo);
return schema.map((item, idx) =>
this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idx),
);
}
const _children = this.getSchemaChildren(schema);
//解析占位组件
if (schema.componentName === 'Flagment' && _children) {
let tarChildren = isJSExpression(_children) ? parseExpression(_children, self) : _children;
return this.__createVirtualDom(tarChildren, self, parentInfo);
}
if (schema.$$typeof) {
return schema;
}
if (!isSchema(schema)) return null;
let Comp = components[schema.componentName] || engine.getNotFoundComponent();
if (schema.hidden) {
return null;
}
if (schema.loop != null) {
return this.__createLoopVirtualDom(
{
...schema,
loop: parseData(schema.loop, self),
},
self,
parentInfo,
idx,
);
}
const condition = schema.condition == null ? true : parseData(schema.condition, self);
if (!condition) return null;
let scopeKey = '';
// 判断组件是否需要生成scope且只生成一次挂在this.__compScopes上
if (Comp.generateScope) {
const key = parseExpression(schema.props.key, self);
if (key) {
// 如果组件自己设置key则使用组件自己的key
scopeKey = key;
} else if (!schema.__ctx) {
// 在生产环境schema没有__ctx上下文需要手动生成一个lunaKey
schema.__ctx = {
lunaKey: `luna${++scopeIdx}`,
};
scopeKey = schema.__ctx.lunaKey;
} else {
// 需要判断循环的情况
scopeKey = schema.__ctx.lunaKey + (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__ = self;
self = compSelf;
}
// 容器类组件的上下文通过props传递避免context传递带来的嵌套问题
const otherProps = isFileSchema(schema)
? {
__schema: schema,
__appHelper: appHelper,
__components: components,
}
: {};
if (engine && engine.props.designMode) {
otherProps.__designMode = engine.props.designMode;
}
const componentInfo = {};
const props = this.__parseProps(schema.props, self, '', {
schema,
Comp,
componentInfo: {
...componentInfo,
props: transformArrayToMap(componentInfo.props, 'name'),
},
}) || {};
// 对于可以获取到ref的组件做特殊处理
if (acceptsRef(Comp)) {
otherProps.ref = (ref) => {
const refProps = props.ref;
if (refProps && typeof refProps === 'string') {
this[refProps] = ref;
}
engine && engine.props.onCompGetRef(schema, ref);
};
}
// scope需要传入到组件上
if (scopeKey && this.__compScopes[scopeKey]) {
props.__scope = this.__compScopes[scopeKey];
}
if (schema.__ctx && schema.__ctx.lunaKey) {
if (!isFileSchema(schema)) {
engine && engine.props.onCompGetCtx(schema, self);
}
props.key = props.key || `${schema.__ctx.lunaKey}_${schema.__ctx.idx || 0}_${idx !== undefined ? idx : ''}`;
} else if (typeof idx === 'number' && !props.key) {
props.key = idx;
}
props.__id = schema.id;
const renderComp = (props) => {
return engine.createElement(
Comp,
props,
(!isFileSchema(schema) &&
!!_children &&
this.__createVirtualDom(
isJSExpression(_children) ? parseExpression(_children, self) : _children,
self,
{
schema,
Comp,
},
)) ||
null,
);
};
//设计模式下的特殊处理
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 (
<Div ref={ref} __designMode={engine.props.designMode}>
{renderComp({ ...props, ...overlayProps })}
</Div>
);
}
// 虚拟dom显示
if (componentInfo && componentInfo.parentRule) {
const parentList = componentInfo.parentRule.split(',');
const { schema: parentSchema, 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 });
} catch (e) {
return engine.createElement(engine.getFaultComponent(), {
error: e,
schema,
self,
parentInfo,
idx,
});
}
};
__createLoopVirtualDom = (schema, self, parentInfo, idx) => {
if (isFileSchema(schema)) {
console.warn('file type not support Loop');
return null;
}
if (!Array.isArray(schema.loop)) return null;
const itemArg = (schema.loopArgs && schema.loopArgs[0]) || 'item';
const indexArg = (schema.loopArgs && schema.loopArgs[1]) || 'index';
return schema.loop.map((item, i) => {
const loopSelf = {
[itemArg]: item,
[indexArg]: i,
};
loopSelf.__proto__ = self;
return this.__createVirtualDom(
{
...schema,
loop: undefined,
},
loopSelf,
parentInfo,
idx ? `${idx}_${i}` : i,
);
});
};
__parseProps = (props, self, path, info) => {
const { schema, Comp, componentInfo = {} } = info;
const propInfo = getValue(componentInfo.props, path);
// FIXME! 将这行逻辑外置,解耦,线上环境不要验证参数,调试环境可以有,通过传参自定义
const propType = propInfo && propInfo.extra && propInfo.extra.propType;
const ignoreParse = schema.__ignoreParse || [];
const checkProps = (value) => {
if (!propType) return value;
return checkPropTypes(value, path, propType, componentInfo.name) ? value : undefined;
};
const parseReactNode = (data, params) => {
if (isEmpty(params)) {
return checkProps(this.__createVirtualDom(data, self, { schema, Comp }));
} else {
return checkProps(function() {
const args = {};
if (Array.isArray(params) && params.length) {
params.map((item, idx) => {
if (typeof item === 'string') {
args[item] = arguments[idx];
} else if (item && typeof item === 'object') {
args[item.name] = arguments[idx];
}
});
}
args.__proto__ = self;
return self.__createVirtualDom(data, args, { schema, Comp });
});
}
};
// 判断是否需要解析变量
if (
ignoreParse.some((item) => {
if (item instanceof RegExp) {
return item.test(path);
}
return item === path;
})
) {
return checkProps(props);
}
if (isJSExpression(props)) {
props = parseExpression(props, self);
// 只有当变量解析出来为模型结构的时候才会继续解析
if (!isSchema(props) && !isJSSlot(props)) return checkProps(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 &&
propInfo.type === 'ReactNode' &&
propInfo.props &&
propInfo.props.type === 'function'
);
const isMixinReactNodeFunction = !!(
propInfo &&
propInfo.type === 'Mixin' &&
propInfo.props &&
propInfo.props.types &&
propInfo.props.types.indexOf('ReactNode') > -1 &&
propInfo.props.reactNodeProps &&
propInfo.props.reactNodeProps.type === 'function'
);
return parseReactNode(
props,
isReactNodeFunction
? propInfo.props.params
: isMixinReactNodeFunction
? propInfo.props.reactNodeProps.params
: null,
);
} else if (Array.isArray(props)) {
return checkProps(props.map((item, idx) => this.__parseProps(item, self, path ? `${path}.${idx}` : idx, info)));
} else if (typeof props === 'function') {
return checkProps(props.bind(self));
} else if (props && typeof props === 'object') {
if (props.$$typeof) return checkProps(props);
const res = {};
forEach(props, (val, key) => {
if (key.startsWith('__')) {
res[key] = val;
return;
}
res[key] = this.__parseProps(val, self, path ? `${path}.${key}` : key, info);
});
return checkProps(res);
} else if (typeof props === 'string') {
return checkProps(props.trim());
}
return checkProps(props);
};
get utils() {
return this.appHelper && this.appHelper.utils;
}
get constants() {
return this.appHelper && this.appHelper.constants;
}
get history() {
return this.appHelper && this.appHelper.history;
}
get location() {
return this.appHelper && this.appHelper.location;
}
get match() {
return this.appHelper && this.appHelper.match;
}
render() {
return null;
}
}