fix: 修复 forwardRef 组件的错误无法捕获

This commit is contained in:
rainke 2023-10-11 14:55:05 +08:00 committed by 刘菊萍(絮黎)
parent 3129445d60
commit ca6fe7c335
6 changed files with 75 additions and 66 deletions

View File

@ -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}
> >

View File

@ -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 cloneEnumerableProperty(
forwardRef((props: any, ref: any) => {
return createElement(Wrapper, { ...props, forwardRef: ref }); return createElement(Wrapper, { ...props, forwardRef: ref });
}), Comp); }),
Comp,
);
} }

View File

@ -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)) {
Comp = compWrapper(Comp);
components[schema.componentName] = 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

View File

@ -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);
} }

View File

@ -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;

View File

@ -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;