diff --git a/packages/amis-core/src/Root.tsx b/packages/amis-core/src/Root.tsx index 75f0ba8dd..fcca69503 100644 --- a/packages/amis-core/src/Root.tsx +++ b/packages/amis-core/src/Root.tsx @@ -11,7 +11,11 @@ import {ThemeContext} from './theme'; import {Schema, SchemaNode} from './types'; import {autobind, isEmpty} from './utils/helper'; import {RootStoreContext} from './WithRootStore'; -import {StatusScoped, StatusScopedProps} from './StatusScoped'; +import { + StatusScoped, + StatusScopedWrapper, + StatusScopedProps +} from './StatusScoped'; export interface RootRenderProps { location?: Location; @@ -154,8 +158,6 @@ export interface renderChildProps } export type ReactElement = React.ReactNode[] | JSX.Element | null | false; -const StatusScopedSchemaRenderer = StatusScoped(SchemaRenderer); - export function renderChildren( prefix: string, node: SchemaNode, @@ -206,6 +208,8 @@ export function renderChild( props = transform(props); } + const Comp = props.env.SchemaRenderer || SchemaRenderer; + if ( ['dialog', 'drawer'].includes(schema?.type) && !schema?.component && @@ -216,18 +220,26 @@ export function renderChild( // 所以这里先根据 type 来处理一下 // 等后续把状态处理再抽一层,可以把此处放到 SchemaRenderer 里面去 return ( - + + {({statusStore}) => ( + + )} + ); } return ( - { schema: any; path: string; - reaction: any; + toDispose: Array<() => any> = []; unbindEvent: (() => void) | undefined = undefined; isStatic: any = undefined; @@ -99,22 +99,24 @@ export class SchemaRenderer extends React.Component { this.dispatchEvent = this.dispatchEvent.bind(this); // 监听statusStore更新 - this.reaction = reaction( - () => { - const id = filter(props.schema.id, props.data); - const name = filter(props.schema.name, props.data); - return `${ - props.statusStore.visibleState[id] ?? - props.statusStore.visibleState[name] - }${ - props.statusStore.disableState[id] ?? - props.statusStore.disableState[name] - }${ - props.statusStore.staticState[id] ?? - props.statusStore.staticState[name] - }`; - }, - () => this.forceUpdate() + this.toDispose.push( + reaction( + () => { + const id = filter(props.schema.id, props.data); + const name = filter(props.schema.name, props.data); + return `${ + props.statusStore.visibleState[id] ?? + props.statusStore.visibleState[name] + }${ + props.statusStore.disableState[id] ?? + props.statusStore.disableState[name] + }${ + props.statusStore.staticState[id] ?? + props.statusStore.staticState[name] + }`; + }, + () => this.forceUpdate() + ) ); } @@ -124,7 +126,8 @@ export class SchemaRenderer extends React.Component { } componentWillUnmount() { - this.reaction?.(); + this.toDispose.forEach(fn => fn()); + this.toDispose = []; this.unbindEvent?.(); } diff --git a/packages/amis-core/src/StatusScoped.tsx b/packages/amis-core/src/StatusScoped.tsx index d19518148..cf7fadbaa 100644 --- a/packages/amis-core/src/StatusScoped.tsx +++ b/packages/amis-core/src/StatusScoped.tsx @@ -8,80 +8,43 @@ export interface StatusScopedProps { statusStore: IStatusStore; } +export interface StatusScopedWrapperProps { + children: (props: {statusStore: IStatusStore}) => JSX.Element; +} + +export function StatusScopedWrapper({children}: StatusScopedWrapperProps) { + const store = React.useMemo(() => StatusStore.create({}), []); + React.useEffect(() => { + return () => { + destroy(store); + }; + }, []); + + return children({statusStore: store}); +} + export function StatusScoped< T extends React.ComponentType & StatusScopedProps> >(ComposedComponent: T) { - type OuterProps = JSX.LibraryManagedAttributes< - T, - Omit, keyof StatusScopedProps> - > & {}; - - const result = hoistNonReactStatic( - class extends React.Component { - static displayName = `StatusScoped(${ - ComposedComponent.displayName || ComposedComponent.name - })`; - static ComposedComponent = ComposedComponent as React.ComponentType; - - store?: IStatusStore; - - constructor(props: OuterProps) { - super(props); - - this.childRef = this.childRef.bind(this); - this.getWrappedInstance = this.getWrappedInstance.bind(this); - - this.store = StatusStore.create({}); - } - - ref: any; - - childRef(ref: any) { - while (ref && ref.getWrappedInstance) { - ref = ref.getWrappedInstance(); - } - - this.ref = ref; - } - - getWrappedInstance() { - return this.ref; - } - - componentWillUnmount(): void { - this.store && destroy(this.store); - delete this.store; - } - - render() { - const injectedProps: { - statusStore: IStatusStore; - } = { - statusStore: this.store! - }; - const refConfig = - ComposedComponent.prototype?.isReactComponent || - (ComposedComponent as any).$$typeof === - Symbol.for('react.forward_ref') - ? {ref: this.childRef} - : {forwardedRef: this.childRef}; - - return ( + const wrapped = ( + props: JSX.LibraryManagedAttributes< + T, + Omit, keyof StatusScopedProps> + > & {}, + ref: any + ) => { + return ( + + {({statusStore}) => ( - > as any)} - {...injectedProps} - {...refConfig} + {...(props as any)} + statusStore={statusStore} + ref={ref} /> - ); - } - }, - ComposedComponent - ); - - return result as typeof result & { - ComposedComponent: T; + )} + + ); }; + + return React.forwardRef(wrapped as any) as typeof wrapped; } diff --git a/packages/amis-core/src/env.tsx b/packages/amis-core/src/env.tsx index 7fb92a688..8238b84ca 100644 --- a/packages/amis-core/src/env.tsx +++ b/packages/amis-core/src/env.tsx @@ -163,6 +163,11 @@ export interface RendererEnv { action: ICmptAction, event: RendererEvent ) => Promise; + + /** + * 渲染器包裹组件可以外部指定 + */ + SchemaRenderer?: React.ComponentType; } export const EnvContext = React.createContext(undefined); diff --git a/packages/amis-core/src/index.tsx b/packages/amis-core/src/index.tsx index ed12e589a..31a9c6ed7 100644 --- a/packages/amis-core/src/index.tsx +++ b/packages/amis-core/src/index.tsx @@ -108,7 +108,10 @@ import { } from './utils/index'; import type {OnEventProps} from './utils/index'; import {valueMap as styleMap} from './utils/style-helper'; -import {RENDERER_TRANSMISSION_OMIT_PROPS} from './SchemaRenderer'; +import { + RENDERER_TRANSMISSION_OMIT_PROPS, + SchemaRenderer +} from './SchemaRenderer'; import type {IItem} from './store/list'; import CustomStyle from './components/CustomStyle'; import {StatusScoped} from './StatusScoped'; @@ -207,7 +210,8 @@ export { CustomStyle, enableDebug, disableDebug, - envOverwrite + envOverwrite, + SchemaRenderer }; export function render( diff --git a/packages/amis-core/src/types.ts b/packages/amis-core/src/types.ts index 619c9772f..a3c468f12 100644 --- a/packages/amis-core/src/types.ts +++ b/packages/amis-core/src/types.ts @@ -262,7 +262,7 @@ export interface fetcherResult { [propName: string]: any; // 为了兼容其他返回格式 }; status: number; - headers: object; + headers?: object; } export interface fetchOptions { diff --git a/packages/amis/src/renderers/Nav.tsx b/packages/amis/src/renderers/Nav.tsx index 029080dc5..a33ca3511 100644 --- a/packages/amis/src/renderers/Nav.tsx +++ b/packages/amis/src/renderers/Nav.tsx @@ -38,7 +38,7 @@ import {BadgeObject} from 'amis-ui'; import {RemoteOptionsProps, withRemoteConfig} from 'amis-ui'; import {Spinner, Menu} from 'amis-ui'; import {ScopedContext, IScopedContext} from 'amis-core'; -import type {NavigationItem} from 'amis-ui/lib/components/menu'; +import type {NavigationItem} from 'amis-ui/lib/components/menu/index'; import type {MenuItemProps} from 'amis-ui/lib/components/menu/MenuItem'; import type {Payload} from 'amis-core'; diff --git a/packages/amis/src/renderers/Service.tsx b/packages/amis/src/renderers/Service.tsx index c181eee29..2565ae86b 100644 --- a/packages/amis/src/renderers/Service.tsx +++ b/packages/amis/src/renderers/Service.tsx @@ -37,6 +37,7 @@ import {IIRendererStore} from 'amis-core'; import type {ListenerAction} from 'amis-core'; import type {ScopedComponentType} from 'amis-core'; import isPlainObject from 'lodash/isPlainObject'; +import {isAlive} from 'mobx-state-tree'; export const eventTypes = [ /* 初始化时执行,默认 */ @@ -529,6 +530,9 @@ export default class Service extends React.Component { // 保存 ajax 请求的时候返回时数据部分。 const data = result?.hasOwnProperty('ok') ? result.data ?? {} : result; const {onBulkChange, dispatchEvent, store, formStore} = this.props; + if (!isAlive(store)) { + return; + } dispatchEvent?.( 'fetchInited',