diff --git a/packages/react-renderer/src/engine/base.jsx b/packages/react-renderer/src/engine/base.jsx
index 14059f015..361c0057b 100644
--- a/packages/react-renderer/src/engine/base.jsx
+++ b/packages/react-renderer/src/engine/base.jsx
@@ -197,168 +197,178 @@ export default class BaseEngine extends PureComponent {
// parentInfo 父组件的信息,包含schema和Comp
// idx 若为循环渲染的循环Index
__createVirtualDom = (schema, self, parentInfo, idx) => {
- if (!schema) return null;
- const { __appHelper: appHelper, __components: components = {} } =
- this.props || {};
- const { engine } = this.context || {};
- 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),
- );
- }
+ try {
+ if (!schema) return null;
+ const { __appHelper: appHelper, __components: components = {} } =
+ this.props || {};
+ const { engine } = this.context || {};
+ 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),
+ );
+ }
- //解析占位组件
- if (schema.componentName === 'Flagment' && schema.children) {
- let tarChildren = isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children;
- return this.__createVirtualDom(tarChildren, self, parentInfo);
- }
+ //解析占位组件
+ if (schema.componentName === 'Flagment' && schema.children) {
+ let tarChildren = isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children;
+ return this.__createVirtualDom(tarChildren, self, parentInfo);
+ }
- if (schema.$$typeof) {
- return schema;
- }
- if (!isSchema(schema)) return null;
+ if (schema.$$typeof) {
+ return schema;
+ }
+ if (!isSchema(schema)) return null;
- let Comp = components[schema.componentName] || Div;
+ let Comp = components[schema.componentName] || engine.getNotFoundComponent();
- if (schema.hidden) {
- return null;
- }
+ if (schema.hidden) {
+ return null;
+ }
- if (schema.loop !== undefined) {
- return this.__createLoopVirtualDom(
- {
- ...schema,
- loop: parseData(schema.loop, self),
+ if (schema.loop !== undefined) {
+ return this.__createLoopVirtualDom(
+ {
+ ...schema,
+ 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 (
+
+ {renderComp({ ...props, ...overlayProps })}
+
+ );
+ }
+ // 虚拟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,
- );
+ });
}
- 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 (
-
- {renderComp({ ...props, ...overlayProps })}
-
- );
- }
- // 虚拟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) => {
diff --git a/packages/react-renderer/src/engine/index.jsx b/packages/react-renderer/src/engine/index.jsx
index 568b0b0b3..cf487549f 100644
--- a/packages/react-renderer/src/engine/index.jsx
+++ b/packages/react-renderer/src/engine/index.jsx
@@ -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 PropTypes from 'prop-types';
import Debug from 'debug';
@@ -11,6 +11,7 @@ import AddonEngine from './addonEngine';
import TempEngine from './tempEngine';
import { isEmpty } from '@ali/b3-one/lib/obj';
import BaseEngine from './base';
+import Div from '@ali/iceluna-comp-div';
window.React = React;
window.ReactDom = ReactDOM;
@@ -23,6 +24,26 @@ const ENGINE_COMPS = {
AddonEngine,
TempEngine,
};
+
+class FaultComponent extends PureComponent {
+ render() {
+ // FIXME: errorlog
+ console.error('render error', this.props);
+ return RenderError
;
+ }
+}
+
+class NotFoundComponent extends PureComponent {
+ render() {
+ console.error('component not found', this.props);
+ return ;
+ }
+}
+
+function isReactClass(obj) {
+ return obj && obj.prototype && (obj.prototype.isReactComponent || obj.prototype instanceof Component);
+}
+
export default class Engine extends PureComponent {
static dislayName = 'engine';
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) {
+ // TODO: enable in runtime mode?
+ this.patchDidCatch(Component);
return (this.props.customCreateElement || reactCreateElement)(Component, props, children);
}
+ getNotFoundComponent() {
+ return this.props.notFoundComponent || NotFoundComponent;
+ }
+ getFaultComponent() {
+ return this.props.faultComponent || FaultComponent;
+ }
render() {
const { schema, designMode, appHelper, components, customCreateElement } = this.props;