From f41d95c8bd532e25d2ab95e1b34daea027bb1439 Mon Sep 17 00:00:00 2001 From: rainke Date: Wed, 11 Oct 2023 14:55:05 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20forwardRef=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E7=9A=84=E9=94=99=E8=AF=AF=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=8D=95=E8=8E=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/renderer-core/src/hoc/index.tsx | 70 ++++++++++++++++--- packages/renderer-core/src/renderer/base.tsx | 8 +-- .../renderer-core/src/renderer/renderer.tsx | 48 ------------- packages/renderer-core/src/types/index.ts | 1 - packages/utils/src/is-react.ts | 13 +++- 5 files changed, 74 insertions(+), 66 deletions(-) diff --git a/packages/renderer-core/src/hoc/index.tsx b/packages/renderer-core/src/hoc/index.tsx index a9314060f..aae03045e 100644 --- a/packages/renderer-core/src/hoc/index.tsx +++ b/packages/renderer-core/src/hoc/index.tsx @@ -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, + ); } diff --git a/packages/renderer-core/src/renderer/base.tsx b/packages/renderer-core/src/renderer/base.tsx index 216735edc..4c02ca6c3 100644 --- a/packages/renderer-core/src/renderer/base.tsx +++ b/packages/renderer-core/src/renderer/base.tsx @@ -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 diff --git a/packages/renderer-core/src/renderer/renderer.tsx b/packages/renderer-core/src/renderer/renderer.tsx index 09559b6f8..300b1cd16 100644 --- a/packages/renderer-core/src/renderer/renderer.tsx +++ b/packages/renderer-core/src/renderer/renderer.tsx @@ -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); } diff --git a/packages/renderer-core/src/types/index.ts b/packages/renderer-core/src/types/index.ts index a49fe8992..afbec272a 100644 --- a/packages/renderer-core/src/types/index.ts +++ b/packages/renderer-core/src/types/index.ts @@ -335,7 +335,6 @@ export interface IRenderComponent { componentDidCatch(e: any): Promise | 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; diff --git a/packages/utils/src/is-react.ts b/packages/utils/src/is-react.ts index 07568db98..b19f043f1 100644 --- a/packages/utils/src/is-react.ts +++ b/packages/utils/src/is-react.ts @@ -10,10 +10,10 @@ export function isReactClass(obj: any): obj is ComponentClass { } 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 { if (!obj) { return false;