mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-14 13:03:07 +00:00
fix: 修复 forwardRef 组件的错误无法捕获
This commit is contained in:
parent
3129445d60
commit
ca6fe7c335
@ -31,6 +31,7 @@ exports[`Base should be render Text 1`] = `
|
|||||||
behavior="NORMAL"
|
behavior="NORMAL"
|
||||||
componentId="node_ockvuu8u916"
|
componentId="node_ockvuu8u916"
|
||||||
fieldId="text_kvuu9gl2"
|
fieldId="text_kvuu9gl2"
|
||||||
|
forwardRef={[Function]}
|
||||||
maxLine={0}
|
maxLine={0}
|
||||||
showTitle={false}
|
showTitle={false}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,20 +1,72 @@
|
|||||||
import { cloneEnumerableProperty } from '@alilc/lowcode-utils';
|
import { cloneEnumerableProperty } from '@alilc/lowcode-utils';
|
||||||
import adapter from '../adapter';
|
import adapter from '../adapter';
|
||||||
|
import { IBaseRendererInstance, IRendererProps } from '../types';
|
||||||
|
|
||||||
export function compWrapper(Comp: any) {
|
function patchDidCatch(Comp: any, { baseRenderer }: { baseRenderer: IBaseRendererInstance }) {
|
||||||
|
if (Comp.patchedCatch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Comp.patchedCatch = true;
|
||||||
|
const { PureComponent } = adapter.getRuntime();
|
||||||
|
// Rax 的 getDerivedStateFromError 有 BUG,这里先用 componentDidCatch 来替代
|
||||||
|
// @see https://github.com/alibaba/rax/issues/2211
|
||||||
|
const originalDidCatch = Comp.prototype.componentDidCatch;
|
||||||
|
Comp.prototype.componentDidCatch = function didCatch(this: any, error: Error, errorInfo: any) {
|
||||||
|
this.setState({ engineRenderError: true, error });
|
||||||
|
if (originalDidCatch && typeof originalDidCatch === 'function') {
|
||||||
|
originalDidCatch.call(this, error, errorInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { engine } = baseRenderer.context;
|
||||||
|
const originRender = Comp.prototype.render;
|
||||||
|
Comp.prototype.render = function () {
|
||||||
|
if (this.state && this.state.engineRenderError) {
|
||||||
|
this.state.engineRenderError = false;
|
||||||
|
return engine.createElement(engine.getFaultComponent(), {
|
||||||
|
...this.props,
|
||||||
|
error: this.state.error,
|
||||||
|
componentName: this.props._componentName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return originRender.call(this);
|
||||||
|
};
|
||||||
|
if (!(Comp.prototype instanceof PureComponent)) {
|
||||||
|
const originShouldComponentUpdate = Comp.prototype.shouldComponentUpdate;
|
||||||
|
Comp.prototype.shouldComponentUpdate = function (nextProps: IRendererProps, nextState: any) {
|
||||||
|
if (nextState && nextState.engineRenderError) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return originShouldComponentUpdate
|
||||||
|
? originShouldComponentUpdate.call(this, nextProps, nextState)
|
||||||
|
: true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compWrapper(Comp: any, options: { baseRenderer: IBaseRendererInstance }) {
|
||||||
const { createElement, Component, forwardRef } = adapter.getRuntime();
|
const { createElement, Component, forwardRef } = adapter.getRuntime();
|
||||||
|
if (
|
||||||
|
Comp?.prototype?.isReactComponent || // react
|
||||||
|
Comp?.prototype?.setState || // rax
|
||||||
|
Comp?.prototype instanceof Component
|
||||||
|
) {
|
||||||
|
patchDidCatch(Comp, options);
|
||||||
|
return Comp;
|
||||||
|
}
|
||||||
class Wrapper extends Component {
|
class Wrapper extends Component {
|
||||||
// constructor(props: any, context: any) {
|
|
||||||
// super(props, context);
|
|
||||||
// }
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return createElement(Comp, this.props);
|
return createElement(Comp, { ...this.props, ref: this.props.forwardRef });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Wrapper as any).displayName = Comp.displayName;
|
(Wrapper as any).displayName = Comp.displayName;
|
||||||
|
|
||||||
return cloneEnumerableProperty(forwardRef((props: any, ref: any) => {
|
patchDidCatch(Wrapper, options);
|
||||||
return createElement(Wrapper, { ...props, forwardRef: ref });
|
|
||||||
}), Comp);
|
return cloneEnumerableProperty(
|
||||||
|
forwardRef((props: any, ref: any) => {
|
||||||
|
return createElement(Wrapper, { ...props, forwardRef: ref });
|
||||||
|
}),
|
||||||
|
Comp,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import {
|
|||||||
transformStringToFunction,
|
transformStringToFunction,
|
||||||
checkPropTypes,
|
checkPropTypes,
|
||||||
getI18n,
|
getI18n,
|
||||||
canAcceptsRef,
|
|
||||||
getFileCssName,
|
getFileCssName,
|
||||||
capitalizeFirstLetter,
|
capitalizeFirstLetter,
|
||||||
DataHelper,
|
DataHelper,
|
||||||
@ -616,11 +615,8 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 对于不可以接收到 ref 的组件需要做特殊处理
|
Comp = compWrapper(Comp, { baseRenderer: this });
|
||||||
if (!canAcceptsRef(Comp)) {
|
components[schema.componentName] = Comp;
|
||||||
Comp = compWrapper(Comp);
|
|
||||||
components[schema.componentName] = Comp;
|
|
||||||
}
|
|
||||||
|
|
||||||
otherProps.ref = (ref: any) => {
|
otherProps.ref = (ref: any) => {
|
||||||
this.$(props.fieldId || props.ref, ref); // 收集ref
|
this.$(props.fieldId || props.ref, ref); // 收集ref
|
||||||
|
|||||||
@ -105,55 +105,7 @@ export default function rendererFactory(): IRenderComponent {
|
|||||||
return SetComponent;
|
return SetComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
patchDidCatch(SetComponent: any) {
|
|
||||||
if (!this.isValidComponent(SetComponent)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (SetComponent.patchedCatch) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!SetComponent.prototype) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SetComponent.patchedCatch = true;
|
|
||||||
|
|
||||||
// Rax 的 getDerivedStateFromError 有 BUG,这里先用 componentDidCatch 来替代
|
|
||||||
// @see https://github.com/alibaba/rax/issues/2211
|
|
||||||
const originalDidCatch = SetComponent.prototype.componentDidCatch;
|
|
||||||
SetComponent.prototype.componentDidCatch = function didCatch(this: any, error: Error, errorInfo: any) {
|
|
||||||
this.setState({ engineRenderError: true, error });
|
|
||||||
if (originalDidCatch && typeof originalDidCatch === 'function') {
|
|
||||||
originalDidCatch.call(this, error, errorInfo);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const engine = this;
|
|
||||||
const originRender = SetComponent.prototype.render;
|
|
||||||
SetComponent.prototype.render = function () {
|
|
||||||
if (this.state && this.state.engineRenderError) {
|
|
||||||
this.state.engineRenderError = false;
|
|
||||||
return engine.createElement(engine.getFaultComponent(), {
|
|
||||||
...this.props,
|
|
||||||
error: this.state.error,
|
|
||||||
componentName: this.props._componentName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return originRender.call(this);
|
|
||||||
};
|
|
||||||
if(!(SetComponent.prototype instanceof PureComponent)) {
|
|
||||||
const originShouldComponentUpdate = SetComponent.prototype.shouldComponentUpdate;
|
|
||||||
SetComponent.prototype.shouldComponentUpdate = function (nextProps: IRendererProps, nextState: any) {
|
|
||||||
if (nextState && nextState.engineRenderError) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return originShouldComponentUpdate ? originShouldComponentUpdate.call(this, nextProps, nextState) : true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createElement(SetComponent: any, props: any, children?: any) {
|
createElement(SetComponent: any, props: any, children?: any) {
|
||||||
// TODO: enable in runtime mode?
|
|
||||||
this.patchDidCatch(SetComponent);
|
|
||||||
return (this.props.customCreateElement || createElement)(SetComponent, props, children);
|
return (this.props.customCreateElement || createElement)(SetComponent, props, children);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -335,7 +335,6 @@ export interface IRenderComponent {
|
|||||||
componentDidCatch(e: any): Promise<void> | void;
|
componentDidCatch(e: any): Promise<void> | void;
|
||||||
shouldComponentUpdate(nextProps: IRendererProps): boolean;
|
shouldComponentUpdate(nextProps: IRendererProps): boolean;
|
||||||
isValidComponent(SetComponent: any): any;
|
isValidComponent(SetComponent: any): any;
|
||||||
patchDidCatch(SetComponent: any): void;
|
|
||||||
createElement(SetComponent: any, props: any, children?: any): any;
|
createElement(SetComponent: any, props: any, children?: any): any;
|
||||||
getNotFoundComponent(): any;
|
getNotFoundComponent(): any;
|
||||||
getFaultComponent(): any;
|
getFaultComponent(): any;
|
||||||
|
|||||||
@ -10,10 +10,10 @@ export function isReactClass(obj: any): obj is ComponentClass<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function acceptsRef(obj: any): boolean {
|
export function acceptsRef(obj: any): boolean {
|
||||||
return obj?.prototype?.isReactComponent || (obj.$$typeof && obj.$$typeof === REACT_FORWARD_REF_TYPE);
|
return obj?.prototype?.isReactComponent || isForwardOrMemoForward(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isForwardRefType(obj: any): boolean {
|
export function isForwardRefType(obj: any): boolean {
|
||||||
return obj?.$$typeof && obj?.$$typeof === REACT_FORWARD_REF_TYPE;
|
return obj?.$$typeof && obj?.$$typeof === REACT_FORWARD_REF_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,6 +21,15 @@ function isMemoType(obj: any): boolean {
|
|||||||
return obj?.$$typeof && obj.$$typeof === REACT_MEMO_TYPE;
|
return obj?.$$typeof && obj.$$typeof === REACT_MEMO_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isForwardOrMemoForward(obj: any): boolean {
|
||||||
|
return obj?.$$typeof && (
|
||||||
|
// React.forwardRef(..)
|
||||||
|
isForwardRefType(obj) ||
|
||||||
|
// React.memo(React.forwardRef(..))
|
||||||
|
(isMemoType(obj) && isForwardRefType(obj.type))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function isReactComponent(obj: any): obj is ComponentType<any> {
|
export function isReactComponent(obj: any): obj is ComponentType<any> {
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user