mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:39:05 +08:00
feat: 渲染器包裹支持外层自定义 (#11138)
This commit is contained in:
parent
38b667f03f
commit
2cb217aa36
@ -11,7 +11,11 @@ import {ThemeContext} from './theme';
|
|||||||
import {Schema, SchemaNode} from './types';
|
import {Schema, SchemaNode} from './types';
|
||||||
import {autobind, isEmpty} from './utils/helper';
|
import {autobind, isEmpty} from './utils/helper';
|
||||||
import {RootStoreContext} from './WithRootStore';
|
import {RootStoreContext} from './WithRootStore';
|
||||||
import {StatusScoped, StatusScopedProps} from './StatusScoped';
|
import {
|
||||||
|
StatusScoped,
|
||||||
|
StatusScopedWrapper,
|
||||||
|
StatusScopedProps
|
||||||
|
} from './StatusScoped';
|
||||||
|
|
||||||
export interface RootRenderProps {
|
export interface RootRenderProps {
|
||||||
location?: Location;
|
location?: Location;
|
||||||
@ -154,8 +158,6 @@ export interface renderChildProps
|
|||||||
}
|
}
|
||||||
export type ReactElement = React.ReactNode[] | JSX.Element | null | false;
|
export type ReactElement = React.ReactNode[] | JSX.Element | null | false;
|
||||||
|
|
||||||
const StatusScopedSchemaRenderer = StatusScoped(SchemaRenderer);
|
|
||||||
|
|
||||||
export function renderChildren(
|
export function renderChildren(
|
||||||
prefix: string,
|
prefix: string,
|
||||||
node: SchemaNode,
|
node: SchemaNode,
|
||||||
@ -206,6 +208,8 @@ export function renderChild(
|
|||||||
props = transform(props);
|
props = transform(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Comp = props.env.SchemaRenderer || SchemaRenderer;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
['dialog', 'drawer'].includes(schema?.type) &&
|
['dialog', 'drawer'].includes(schema?.type) &&
|
||||||
!schema?.component &&
|
!schema?.component &&
|
||||||
@ -216,18 +220,26 @@ export function renderChild(
|
|||||||
// 所以这里先根据 type 来处理一下
|
// 所以这里先根据 type 来处理一下
|
||||||
// 等后续把状态处理再抽一层,可以把此处放到 SchemaRenderer 里面去
|
// 等后续把状态处理再抽一层,可以把此处放到 SchemaRenderer 里面去
|
||||||
return (
|
return (
|
||||||
<StatusScopedSchemaRenderer
|
<StatusScopedWrapper>
|
||||||
render={renderChild as any}
|
{({statusStore}) => (
|
||||||
{...props}
|
<Comp
|
||||||
schema={schema}
|
render={renderChild as any}
|
||||||
propKey={schema.key}
|
{...props}
|
||||||
$path={`${prefix ? `${prefix}/` : ''}${(schema && schema.type) || ''}`}
|
key={props.key ?? schema.key}
|
||||||
/>
|
schema={schema}
|
||||||
|
propKey={schema.key}
|
||||||
|
$path={`${prefix ? `${prefix}/` : ''}${
|
||||||
|
(schema && schema.type) || ''
|
||||||
|
}`}
|
||||||
|
statusStore={statusStore}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</StatusScopedWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaRenderer
|
<Comp
|
||||||
render={renderChild as any}
|
render={renderChild as any}
|
||||||
{...props}
|
{...props}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
|
@ -86,7 +86,7 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
|
|||||||
schema: any;
|
schema: any;
|
||||||
path: string;
|
path: string;
|
||||||
|
|
||||||
reaction: any;
|
toDispose: Array<() => any> = [];
|
||||||
unbindEvent: (() => void) | undefined = undefined;
|
unbindEvent: (() => void) | undefined = undefined;
|
||||||
isStatic: any = undefined;
|
isStatic: any = undefined;
|
||||||
|
|
||||||
@ -99,22 +99,24 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
|
|||||||
this.dispatchEvent = this.dispatchEvent.bind(this);
|
this.dispatchEvent = this.dispatchEvent.bind(this);
|
||||||
|
|
||||||
// 监听statusStore更新
|
// 监听statusStore更新
|
||||||
this.reaction = reaction(
|
this.toDispose.push(
|
||||||
() => {
|
reaction(
|
||||||
const id = filter(props.schema.id, props.data);
|
() => {
|
||||||
const name = filter(props.schema.name, props.data);
|
const id = filter(props.schema.id, props.data);
|
||||||
return `${
|
const name = filter(props.schema.name, props.data);
|
||||||
props.statusStore.visibleState[id] ??
|
return `${
|
||||||
props.statusStore.visibleState[name]
|
props.statusStore.visibleState[id] ??
|
||||||
}${
|
props.statusStore.visibleState[name]
|
||||||
props.statusStore.disableState[id] ??
|
}${
|
||||||
props.statusStore.disableState[name]
|
props.statusStore.disableState[id] ??
|
||||||
}${
|
props.statusStore.disableState[name]
|
||||||
props.statusStore.staticState[id] ??
|
}${
|
||||||
props.statusStore.staticState[name]
|
props.statusStore.staticState[id] ??
|
||||||
}`;
|
props.statusStore.staticState[name]
|
||||||
},
|
}`;
|
||||||
() => this.forceUpdate()
|
},
|
||||||
|
() => this.forceUpdate()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +126,8 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.reaction?.();
|
this.toDispose.forEach(fn => fn());
|
||||||
|
this.toDispose = [];
|
||||||
this.unbindEvent?.();
|
this.unbindEvent?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,80 +8,43 @@ export interface StatusScopedProps {
|
|||||||
statusStore: IStatusStore;
|
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<
|
export function StatusScoped<
|
||||||
T extends React.ComponentType<React.ComponentProps<T> & StatusScopedProps>
|
T extends React.ComponentType<React.ComponentProps<T> & StatusScopedProps>
|
||||||
>(ComposedComponent: T) {
|
>(ComposedComponent: T) {
|
||||||
type OuterProps = JSX.LibraryManagedAttributes<
|
const wrapped = (
|
||||||
T,
|
props: JSX.LibraryManagedAttributes<
|
||||||
Omit<React.ComponentProps<T>, keyof StatusScopedProps>
|
T,
|
||||||
> & {};
|
Omit<React.ComponentProps<T>, keyof StatusScopedProps>
|
||||||
|
> & {},
|
||||||
const result = hoistNonReactStatic(
|
ref: any
|
||||||
class extends React.Component<OuterProps> {
|
) => {
|
||||||
static displayName = `StatusScoped(${
|
return (
|
||||||
ComposedComponent.displayName || ComposedComponent.name
|
<StatusScopedWrapper>
|
||||||
})`;
|
{({statusStore}) => (
|
||||||
static ComposedComponent = ComposedComponent as React.ComponentType<T>;
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<ComposedComponent
|
<ComposedComponent
|
||||||
{...(this.props as JSX.LibraryManagedAttributes<
|
{...(props as any)}
|
||||||
T,
|
statusStore={statusStore}
|
||||||
React.ComponentProps<T>
|
ref={ref}
|
||||||
> as any)}
|
|
||||||
{...injectedProps}
|
|
||||||
{...refConfig}
|
|
||||||
/>
|
/>
|
||||||
);
|
)}
|
||||||
}
|
</StatusScopedWrapper>
|
||||||
},
|
);
|
||||||
ComposedComponent
|
|
||||||
);
|
|
||||||
|
|
||||||
return result as typeof result & {
|
|
||||||
ComposedComponent: T;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return React.forwardRef(wrapped as any) as typeof wrapped;
|
||||||
}
|
}
|
||||||
|
@ -163,6 +163,11 @@ export interface RendererEnv {
|
|||||||
action: ICmptAction,
|
action: ICmptAction,
|
||||||
event: RendererEvent<any, any>
|
event: RendererEvent<any, any>
|
||||||
) => Promise<void | boolean>;
|
) => Promise<void | boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染器包裹组件可以外部指定
|
||||||
|
*/
|
||||||
|
SchemaRenderer?: React.ComponentType<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EnvContext = React.createContext<RendererEnv | void>(undefined);
|
export const EnvContext = React.createContext<RendererEnv | void>(undefined);
|
||||||
|
@ -108,7 +108,10 @@ import {
|
|||||||
} from './utils/index';
|
} from './utils/index';
|
||||||
import type {OnEventProps} from './utils/index';
|
import type {OnEventProps} from './utils/index';
|
||||||
import {valueMap as styleMap} from './utils/style-helper';
|
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 type {IItem} from './store/list';
|
||||||
import CustomStyle from './components/CustomStyle';
|
import CustomStyle from './components/CustomStyle';
|
||||||
import {StatusScoped} from './StatusScoped';
|
import {StatusScoped} from './StatusScoped';
|
||||||
@ -207,7 +210,8 @@ export {
|
|||||||
CustomStyle,
|
CustomStyle,
|
||||||
enableDebug,
|
enableDebug,
|
||||||
disableDebug,
|
disableDebug,
|
||||||
envOverwrite
|
envOverwrite,
|
||||||
|
SchemaRenderer
|
||||||
};
|
};
|
||||||
|
|
||||||
export function render(
|
export function render(
|
||||||
|
@ -262,7 +262,7 @@ export interface fetcherResult {
|
|||||||
[propName: string]: any; // 为了兼容其他返回格式
|
[propName: string]: any; // 为了兼容其他返回格式
|
||||||
};
|
};
|
||||||
status: number;
|
status: number;
|
||||||
headers: object;
|
headers?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface fetchOptions {
|
export interface fetchOptions {
|
||||||
|
@ -38,7 +38,7 @@ import {BadgeObject} from 'amis-ui';
|
|||||||
import {RemoteOptionsProps, withRemoteConfig} from 'amis-ui';
|
import {RemoteOptionsProps, withRemoteConfig} from 'amis-ui';
|
||||||
import {Spinner, Menu} from 'amis-ui';
|
import {Spinner, Menu} from 'amis-ui';
|
||||||
import {ScopedContext, IScopedContext} from 'amis-core';
|
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 {MenuItemProps} from 'amis-ui/lib/components/menu/MenuItem';
|
||||||
|
|
||||||
import type {Payload} from 'amis-core';
|
import type {Payload} from 'amis-core';
|
||||||
|
@ -37,6 +37,7 @@ import {IIRendererStore} from 'amis-core';
|
|||||||
import type {ListenerAction} from 'amis-core';
|
import type {ListenerAction} from 'amis-core';
|
||||||
import type {ScopedComponentType} from 'amis-core';
|
import type {ScopedComponentType} from 'amis-core';
|
||||||
import isPlainObject from 'lodash/isPlainObject';
|
import isPlainObject from 'lodash/isPlainObject';
|
||||||
|
import {isAlive} from 'mobx-state-tree';
|
||||||
|
|
||||||
export const eventTypes = [
|
export const eventTypes = [
|
||||||
/* 初始化时执行,默认 */
|
/* 初始化时执行,默认 */
|
||||||
@ -529,6 +530,9 @@ export default class Service extends React.Component<ServiceProps> {
|
|||||||
// 保存 ajax 请求的时候返回时数据部分。
|
// 保存 ajax 请求的时候返回时数据部分。
|
||||||
const data = result?.hasOwnProperty('ok') ? result.data ?? {} : result;
|
const data = result?.hasOwnProperty('ok') ? result.data ?? {} : result;
|
||||||
const {onBulkChange, dispatchEvent, store, formStore} = this.props;
|
const {onBulkChange, dispatchEvent, store, formStore} = this.props;
|
||||||
|
if (!isAlive(store)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dispatchEvent?.(
|
dispatchEvent?.(
|
||||||
'fetchInited',
|
'fetchInited',
|
||||||
|
Loading…
Reference in New Issue
Block a user