mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-11 10:34:02 +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"
|
||||
componentId="node_ockvuu8u916"
|
||||
fieldId="text_kvuu9gl2"
|
||||
forwardRef={[Function]}
|
||||
maxLine={0}
|
||||
showTitle={false}
|
||||
>
|
||||
|
||||
@ -1,20 +1,72 @@
|
||||
import { cloneEnumerableProperty } from '@alilc/lowcode-utils';
|
||||
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();
|
||||
if (
|
||||
Comp?.prototype?.isReactComponent || // react
|
||||
Comp?.prototype?.setState || // rax
|
||||
Comp?.prototype instanceof Component
|
||||
) {
|
||||
patchDidCatch(Comp, options);
|
||||
return Comp;
|
||||
}
|
||||
class Wrapper extends Component {
|
||||
// constructor(props: any, context: any) {
|
||||
// super(props, context);
|
||||
// }
|
||||
|
||||
render() {
|
||||
return createElement(Comp, this.props);
|
||||
return createElement(Comp, { ...this.props, ref: this.props.forwardRef });
|
||||
}
|
||||
}
|
||||
(Wrapper as any).displayName = Comp.displayName;
|
||||
|
||||
return cloneEnumerableProperty(forwardRef((props: any, ref: any) => {
|
||||
return createElement(Wrapper, { ...props, forwardRef: ref });
|
||||
}), Comp);
|
||||
patchDidCatch(Wrapper, options);
|
||||
|
||||
return cloneEnumerableProperty(
|
||||
forwardRef((props: any, ref: any) => {
|
||||
return createElement(Wrapper, { ...props, forwardRef: ref });
|
||||
}),
|
||||
Comp,
|
||||
);
|
||||
}
|
||||
|
||||
@ -23,7 +23,6 @@ import {
|
||||
transformStringToFunction,
|
||||
checkPropTypes,
|
||||
getI18n,
|
||||
canAcceptsRef,
|
||||
getFileCssName,
|
||||
capitalizeFirstLetter,
|
||||
DataHelper,
|
||||
@ -616,11 +615,8 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
||||
});
|
||||
});
|
||||
|
||||
// 对于不可以接收到 ref 的组件需要做特殊处理
|
||||
if (!canAcceptsRef(Comp)) {
|
||||
Comp = compWrapper(Comp);
|
||||
components[schema.componentName] = Comp;
|
||||
}
|
||||
Comp = compWrapper(Comp, { baseRenderer: this });
|
||||
components[schema.componentName] = Comp;
|
||||
|
||||
otherProps.ref = (ref: any) => {
|
||||
this.$(props.fieldId || props.ref, ref); // 收集ref
|
||||
|
||||
@ -105,55 +105,7 @@ export default function rendererFactory(): IRenderComponent {
|
||||
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) {
|
||||
// TODO: enable in runtime mode?
|
||||
this.patchDidCatch(SetComponent);
|
||||
return (this.props.customCreateElement || createElement)(SetComponent, props, children);
|
||||
}
|
||||
|
||||
|
||||
@ -335,7 +335,6 @@ export interface IRenderComponent {
|
||||
componentDidCatch(e: any): Promise<void> | void;
|
||||
shouldComponentUpdate(nextProps: IRendererProps): boolean;
|
||||
isValidComponent(SetComponent: any): any;
|
||||
patchDidCatch(SetComponent: any): void;
|
||||
createElement(SetComponent: any, props: any, children?: any): any;
|
||||
getNotFoundComponent(): any;
|
||||
getFaultComponent(): any;
|
||||
|
||||
@ -10,10 +10,10 @@ export function isReactClass(obj: any): obj is ComponentClass<any> {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -21,6 +21,15 @@ function isMemoType(obj: any): boolean {
|
||||
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> {
|
||||
if (!obj) {
|
||||
return false;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user