Skip to content

Commit f41d95c

Browse files
committed
fix: 修复 forwardRef 组件的错误无法捕获
1 parent 4684d31 commit f41d95c

File tree

5 files changed

+74
-66
lines changed

5 files changed

+74
-66
lines changed
Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,72 @@
11
import { cloneEnumerableProperty } from '@alilc/lowcode-utils';
22
import adapter from '../adapter';
3+
import { IBaseRendererInstance, IRendererProps } from '../types';
34

4-
export function compWrapper(Comp: any) {
5+
function patchDidCatch(Comp: any, { baseRenderer }: { baseRenderer: IBaseRendererInstance }) {
6+
if (Comp.patchedCatch) {
7+
return;
8+
}
9+
Comp.patchedCatch = true;
10+
const { PureComponent } = adapter.getRuntime();
11+
// Rax 的 getDerivedStateFromError 有 BUG,这里先用 componentDidCatch 来替代
12+
// @see https://github.com/alibaba/rax/issues/2211
13+
const originalDidCatch = Comp.prototype.componentDidCatch;
14+
Comp.prototype.componentDidCatch = function didCatch(this: any, error: Error, errorInfo: any) {
15+
this.setState({ engineRenderError: true, error });
16+
if (originalDidCatch && typeof originalDidCatch === 'function') {
17+
originalDidCatch.call(this, error, errorInfo);
18+
}
19+
};
20+
21+
const { engine } = baseRenderer.context;
22+
const originRender = Comp.prototype.render;
23+
Comp.prototype.render = function () {
24+
if (this.state && this.state.engineRenderError) {
25+
this.state.engineRenderError = false;
26+
return engine.createElement(engine.getFaultComponent(), {
27+
...this.props,
28+
error: this.state.error,
29+
componentName: this.props._componentName,
30+
});
31+
}
32+
return originRender.call(this);
33+
};
34+
if (!(Comp.prototype instanceof PureComponent)) {
35+
const originShouldComponentUpdate = Comp.prototype.shouldComponentUpdate;
36+
Comp.prototype.shouldComponentUpdate = function (nextProps: IRendererProps, nextState: any) {
37+
if (nextState && nextState.engineRenderError) {
38+
return true;
39+
}
40+
return originShouldComponentUpdate
41+
? originShouldComponentUpdate.call(this, nextProps, nextState)
42+
: true;
43+
};
44+
}
45+
}
46+
47+
export function compWrapper(Comp: any, options: { baseRenderer: IBaseRendererInstance }) {
548
const { createElement, Component, forwardRef } = adapter.getRuntime();
49+
if (
50+
Comp?.prototype?.isReactComponent || // react
51+
Comp?.prototype?.setState || // rax
52+
Comp?.prototype instanceof Component
53+
) {
54+
patchDidCatch(Comp, options);
55+
return Comp;
56+
}
657
class Wrapper extends Component {
7-
// constructor(props: any, context: any) {
8-
// super(props, context);
9-
// }
10-
1158
render() {
12-
return createElement(Comp, this.props);
59+
return createElement(Comp, { ...this.props, ref: this.props.forwardRef });
1360
}
1461
}
1562
(Wrapper as any).displayName = Comp.displayName;
1663

17-
return cloneEnumerableProperty(forwardRef((props: any, ref: any) => {
18-
return createElement(Wrapper, { ...props, forwardRef: ref });
19-
}), Comp);
64+
patchDidCatch(Wrapper, options);
65+
66+
return cloneEnumerableProperty(
67+
forwardRef((props: any, ref: any) => {
68+
return createElement(Wrapper, { ...props, forwardRef: ref });
69+
}),
70+
Comp,
71+
);
2072
}

packages/renderer-core/src/renderer/base.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import {
2323
transformStringToFunction,
2424
checkPropTypes,
2525
getI18n,
26-
canAcceptsRef,
2726
getFileCssName,
2827
capitalizeFirstLetter,
2928
DataHelper,
@@ -616,11 +615,8 @@ export default function baseRendererFactory(): IBaseRenderComponent {
616615
});
617616
});
618617

619-
// 对于不可以接收到 ref 的组件需要做特殊处理
620-
if (!canAcceptsRef(Comp)) {
621-
Comp = compWrapper(Comp);
622-
components[schema.componentName] = Comp;
623-
}
618+
Comp = compWrapper(Comp, { baseRenderer: this });
619+
components[schema.componentName] = Comp;
624620

625621
otherProps.ref = (ref: any) => {
626622
this.$(props.fieldId || props.ref, ref); // 收集ref

packages/renderer-core/src/renderer/renderer.tsx

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -105,55 +105,7 @@ export default function rendererFactory(): IRenderComponent {
105105
return SetComponent;
106106
}
107107

108-
patchDidCatch(SetComponent: any) {
109-
if (!this.isValidComponent(SetComponent)) {
110-
return;
111-
}
112-
if (SetComponent.patchedCatch) {
113-
return;
114-
}
115-
if (!SetComponent.prototype) {
116-
return;
117-
}
118-
SetComponent.patchedCatch = true;
119-
120-
// Rax 的 getDerivedStateFromError 有 BUG,这里先用 componentDidCatch 来替代
121-
// @see https://github.com/alibaba/rax/issues/2211
122-
const originalDidCatch = SetComponent.prototype.componentDidCatch;
123-
SetComponent.prototype.componentDidCatch = function didCatch(this: any, error: Error, errorInfo: any) {
124-
this.setState({ engineRenderError: true, error });
125-
if (originalDidCatch && typeof originalDidCatch === 'function') {
126-
originalDidCatch.call(this, error, errorInfo);
127-
}
128-
};
129-
130-
const engine = this;
131-
const originRender = SetComponent.prototype.render;
132-
SetComponent.prototype.render = function () {
133-
if (this.state && this.state.engineRenderError) {
134-
this.state.engineRenderError = false;
135-
return engine.createElement(engine.getFaultComponent(), {
136-
...this.props,
137-
error: this.state.error,
138-
componentName: this.props._componentName
139-
});
140-
}
141-
return originRender.call(this);
142-
};
143-
if(!(SetComponent.prototype instanceof PureComponent)) {
144-
const originShouldComponentUpdate = SetComponent.prototype.shouldComponentUpdate;
145-
SetComponent.prototype.shouldComponentUpdate = function (nextProps: IRendererProps, nextState: any) {
146-
if (nextState && nextState.engineRenderError) {
147-
return true;
148-
}
149-
return originShouldComponentUpdate ? originShouldComponentUpdate.call(this, nextProps, nextState) : true;
150-
};
151-
}
152-
}
153-
154108
createElement(SetComponent: any, props: any, children?: any) {
155-
// TODO: enable in runtime mode?
156-
this.patchDidCatch(SetComponent);
157109
return (this.props.customCreateElement || createElement)(SetComponent, props, children);
158110
}
159111

packages/renderer-core/src/types/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,6 @@ export interface IRenderComponent {
335335
componentDidCatch(e: any): Promise<void> | void;
336336
shouldComponentUpdate(nextProps: IRendererProps): boolean;
337337
isValidComponent(SetComponent: any): any;
338-
patchDidCatch(SetComponent: any): void;
339338
createElement(SetComponent: any, props: any, children?: any): any;
340339
getNotFoundComponent(): any;
341340
getFaultComponent(): any;

packages/utils/src/is-react.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,26 @@ export function isReactClass(obj: any): obj is ComponentClass<any> {
1010
}
1111

1212
export function acceptsRef(obj: any): boolean {
13-
return obj?.prototype?.isReactComponent || (obj.$$typeof && obj.$$typeof === REACT_FORWARD_REF_TYPE);
13+
return obj?.prototype?.isReactComponent || isForwardOrMemoForward(obj);
1414
}
1515

16-
function isForwardRefType(obj: any): boolean {
16+
export function isForwardRefType(obj: any): boolean {
1717
return obj?.$$typeof && obj?.$$typeof === REACT_FORWARD_REF_TYPE;
1818
}
1919

2020
function isMemoType(obj: any): boolean {
2121
return obj?.$$typeof && obj.$$typeof === REACT_MEMO_TYPE;
2222
}
2323

24+
export function isForwardOrMemoForward(obj: any): boolean {
25+
return obj?.$$typeof && (
26+
// React.forwardRef(..)
27+
isForwardRefType(obj) ||
28+
// React.memo(React.forwardRef(..))
29+
(isMemoType(obj) && isForwardRefType(obj.type))
30+
);
31+
}
32+
2433
export function isReactComponent(obj: any): obj is ComponentType<any> {
2534
if (!obj) {
2635
return false;

0 commit comments

Comments
 (0)