Skip to content

Commit

Permalink
fix: 修复 forwardRef 组件的错误无法捕获
Browse files Browse the repository at this point in the history
  • Loading branch information
rainke committed Oct 12, 2023
1 parent 4684d31 commit f41d95c
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 66 deletions.
70 changes: 61 additions & 9 deletions packages/renderer-core/src/hoc/index.tsx
Original file line number Diff line number Diff line change
@@ -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,
);
}
8 changes: 2 additions & 6 deletions packages/renderer-core/src/renderer/base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
transformStringToFunction,
checkPropTypes,
getI18n,
canAcceptsRef,
getFileCssName,
capitalizeFirstLetter,
DataHelper,
Expand Down Expand Up @@ -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
Expand Down
48 changes: 0 additions & 48 deletions packages/renderer-core/src/renderer/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
1 change: 0 additions & 1 deletion packages/renderer-core/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 11 additions & 2 deletions packages/utils/src/is-react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,26 @@ 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;
}

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;
Expand Down

0 comments on commit f41d95c

Please sign in to comment.