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;