mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-04-20 12:28:08 +00:00
add error catch
This commit is contained in:
parent
fb918a10be
commit
db35986635
@ -197,168 +197,178 @@ export default class BaseEngine extends PureComponent {
|
|||||||
// parentInfo 父组件的信息,包含schema和Comp
|
// parentInfo 父组件的信息,包含schema和Comp
|
||||||
// idx 若为循环渲染的循环Index
|
// idx 若为循环渲染的循环Index
|
||||||
__createVirtualDom = (schema, self, parentInfo, idx) => {
|
__createVirtualDom = (schema, self, parentInfo, idx) => {
|
||||||
if (!schema) return null;
|
try {
|
||||||
const { __appHelper: appHelper, __components: components = {} } =
|
if (!schema) return null;
|
||||||
this.props || {};
|
const { __appHelper: appHelper, __components: components = {} } =
|
||||||
const { engine } = this.context || {};
|
this.props || {};
|
||||||
if (isJSExpression(schema)) {
|
const { engine } = this.context || {};
|
||||||
return parseExpression(schema, self);
|
if (isJSExpression(schema)) {
|
||||||
}
|
return parseExpression(schema, self);
|
||||||
if (typeof schema === 'string') return schema;
|
}
|
||||||
if (typeof schema === 'number' || typeof schema === 'boolean') {
|
if (typeof schema === 'string') return schema;
|
||||||
return schema.toString();
|
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);
|
if (Array.isArray(schema)) {
|
||||||
return schema.map((item, idx) =>
|
if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo);
|
||||||
this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idx),
|
return schema.map((item, idx) =>
|
||||||
);
|
this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idx),
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
//解析占位组件
|
//解析占位组件
|
||||||
if (schema.componentName === 'Flagment' && schema.children) {
|
if (schema.componentName === 'Flagment' && schema.children) {
|
||||||
let tarChildren = isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children;
|
let tarChildren = isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children;
|
||||||
return this.__createVirtualDom(tarChildren, self, parentInfo);
|
return this.__createVirtualDom(tarChildren, self, parentInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schema.$$typeof) {
|
if (schema.$$typeof) {
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
if (!isSchema(schema)) return null;
|
if (!isSchema(schema)) return null;
|
||||||
|
|
||||||
let Comp = components[schema.componentName] || Div;
|
let Comp = components[schema.componentName] || engine.getNotFoundComponent();
|
||||||
|
|
||||||
if (schema.hidden) {
|
if (schema.hidden) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schema.loop !== undefined) {
|
if (schema.loop !== undefined) {
|
||||||
return this.__createLoopVirtualDom(
|
return this.__createLoopVirtualDom(
|
||||||
{
|
{
|
||||||
...schema,
|
...schema,
|
||||||
loop: parseData(schema.loop, self),
|
loop: parseData(schema.loop, self),
|
||||||
|
},
|
||||||
|
self,
|
||||||
|
parentInfo,
|
||||||
|
idx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const condition = schema.condition === undefined ? 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) &&
|
||||||
|
!!schema.children &&
|
||||||
|
this.__createVirtualDom(
|
||||||
|
isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.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,
|
self,
|
||||||
parentInfo,
|
parentInfo,
|
||||||
idx,
|
idx,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
const condition = schema.condition === undefined ? 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) &&
|
|
||||||
!!schema.children &&
|
|
||||||
this.__createVirtualDom(
|
|
||||||
isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.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 });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
__createLoopVirtualDom = (schema, self, parentInfo, idx) => {
|
__createLoopVirtualDom = (schema, self, parentInfo, idx) => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { PureComponent, createElement as reactCreateElement } from 'react';
|
import React, { Component, PureComponent, createElement as reactCreateElement } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Debug from 'debug';
|
import Debug from 'debug';
|
||||||
@ -11,6 +11,7 @@ import AddonEngine from './addonEngine';
|
|||||||
import TempEngine from './tempEngine';
|
import TempEngine from './tempEngine';
|
||||||
import { isEmpty } from '@ali/b3-one/lib/obj';
|
import { isEmpty } from '@ali/b3-one/lib/obj';
|
||||||
import BaseEngine from './base';
|
import BaseEngine from './base';
|
||||||
|
import Div from '@ali/iceluna-comp-div';
|
||||||
|
|
||||||
window.React = React;
|
window.React = React;
|
||||||
window.ReactDom = ReactDOM;
|
window.ReactDom = ReactDOM;
|
||||||
@ -23,6 +24,26 @@ const ENGINE_COMPS = {
|
|||||||
AddonEngine,
|
AddonEngine,
|
||||||
TempEngine,
|
TempEngine,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class FaultComponent extends PureComponent {
|
||||||
|
render() {
|
||||||
|
// FIXME: errorlog
|
||||||
|
console.error('render error', this.props);
|
||||||
|
return <Div>RenderError</Div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotFoundComponent extends PureComponent {
|
||||||
|
render() {
|
||||||
|
console.error('component not found', this.props);
|
||||||
|
return <Div {...this.props} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReactClass(obj) {
|
||||||
|
return obj && obj.prototype && (obj.prototype.isReactComponent || obj.prototype instanceof Component);
|
||||||
|
}
|
||||||
|
|
||||||
export default class Engine extends PureComponent {
|
export default class Engine extends PureComponent {
|
||||||
static dislayName = 'engine';
|
static dislayName = 'engine';
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -86,9 +107,48 @@ export default class Engine extends PureComponent {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
patchDidCatch(Component) {
|
||||||
|
if (!isReactClass(Component)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Component.patchedCatch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Component.patchedCatch = true;
|
||||||
|
Component.getDerivedStateFromError = (error) => {
|
||||||
|
return { engineRenderError: true, error };
|
||||||
|
};
|
||||||
|
const engine = this;
|
||||||
|
const originRender = Component.prototype.render;
|
||||||
|
Component.prototype.render = function () {
|
||||||
|
if (this.state && this.state.engineRenderError) {
|
||||||
|
return engine.createElement(this.getFaultComponent(), {
|
||||||
|
error: this.state.error,
|
||||||
|
props: this.props,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return originRender.call(this);
|
||||||
|
};
|
||||||
|
const originShouldComponentUpdate = Component.prototype.shouldComponentUpdate;
|
||||||
|
Component.prototype.shouldComponentUpdate = function (nextProps, nextState) {
|
||||||
|
if (nextState && nextState.engineRenderError) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return originShouldComponentUpdate ? originShouldComponentUpdate.call(this, nextProps, nextState) : true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
createElement(Component, props, children) {
|
createElement(Component, props, children) {
|
||||||
|
// TODO: enable in runtime mode?
|
||||||
|
this.patchDidCatch(Component);
|
||||||
return (this.props.customCreateElement || reactCreateElement)(Component, props, children);
|
return (this.props.customCreateElement || reactCreateElement)(Component, props, children);
|
||||||
}
|
}
|
||||||
|
getNotFoundComponent() {
|
||||||
|
return this.props.notFoundComponent || NotFoundComponent;
|
||||||
|
}
|
||||||
|
getFaultComponent() {
|
||||||
|
return this.props.faultComponent || FaultComponent;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { schema, designMode, appHelper, components, customCreateElement } = this.props;
|
const { schema, designMode, appHelper, components, customCreateElement } = this.props;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user