fix: schema component options add memo

This commit is contained in:
dream2023 2024-03-06 15:17:12 +08:00
parent 55d055f801
commit 2c3a04c5c1
13 changed files with 152 additions and 108 deletions

View File

@ -29,15 +29,20 @@ export const ScopeSelect = (props) => {
}), }),
[], [],
); );
const scope = useMemo(() => {
return {
onChange(value) {
props?.onChange?.(value);
},
};
}, [props?.onChange])
return ( return (
<FormProvider form={form}> <FormProvider form={form}>
<SchemaComponent <SchemaComponent
components={{ RolesResourcesScopesSelectedRowKeysProvider }} components={{ RolesResourcesScopesSelectedRowKeysProvider }}
scope={{ scope={scope}
onChange(value) {
props?.onChange?.(value);
},
}}
schema={scopesSchema} schema={scopesSchema}
/> />
</FormProvider> </FormProvider>

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { useMemo } from 'react';
import { Plugin } from '../application/Plugin'; import { Plugin } from '../application/Plugin';
import { ActionSchemaToolbar } from '../modules/actions/ActionSchemaToolbar'; import { ActionSchemaToolbar } from '../modules/actions/ActionSchemaToolbar';
import { CollapseItemSchemaToolbar } from '../modules/blocks/filter-blocks/collapse/CollapseItemSchemaToolbar'; import { CollapseItemSchemaToolbar } from '../modules/blocks/filter-blocks/collapse/CollapseItemSchemaToolbar';
@ -18,30 +18,34 @@ import { BlockSchemaToolbar } from '../modules/blocks/BlockSchemaToolbar';
// TODO: delete this, replaced by `BlockSchemaComponentPlugin` // TODO: delete this, replaced by `BlockSchemaComponentPlugin`
export const BlockSchemaComponentProvider: React.FC = (props) => { export const BlockSchemaComponentProvider: React.FC = (props) => {
const components = useMemo(() => ({
TableFieldProvider,
TableBlockProvider,
TableSelectorProvider,
FormBlockProvider,
FilterFormBlockProvider,
FormFieldProvider,
DetailsBlockProvider,
RecordLink,
}), [])
const scope = useMemo(() => ({
...bp,
useSourceIdFromRecord,
useSourceIdFromParentRecord,
useParamsFromRecord,
useFormBlockProps,
useFormFieldProps,
useDetailsBlockProps,
useTableFieldProps,
useTableBlockProps,
useTableSelectorProps,
}), [])
return ( return (
<SchemaComponentOptions <SchemaComponentOptions
components={{ components={components}
TableFieldProvider, scope={scope}
TableBlockProvider,
TableSelectorProvider,
FormBlockProvider,
FilterFormBlockProvider,
FormFieldProvider,
DetailsBlockProvider,
RecordLink,
}}
scope={{
...bp,
useSourceIdFromRecord,
useSourceIdFromParentRecord,
useParamsFromRecord,
useFormBlockProps,
useFormFieldProps,
useDetailsBlockProps,
useTableFieldProps,
useTableBlockProps,
useTableSelectorProps,
}}
> >
{props.children} {props.children}
</SchemaComponentOptions> </SchemaComponentOptions>

View File

@ -111,8 +111,9 @@ export const TableBlockProvider = (props) => {
}; };
}, [parsedFilter, params]); }, [parsedFilter, params]);
const scope = useMemo(() => ({ treeTable }), [treeTable]);
return ( return (
<SchemaComponentOptions scope={{ treeTable }}> <SchemaComponentOptions scope={scope}>
<FormContext.Provider value={form}> <FormContext.Provider value={form}>
<BlockProvider name={props.name || 'table'} {...props} params={paramsWithFilter} runWhenParamsChanged> <BlockProvider name={props.name || 'table'} {...props} params={paramsWithFilter} runWhenParamsChanged>
<InternalTableBlockProvider {...props} childrenColumnName={childrenColumnName} params={paramsWithFilter} /> <InternalTableBlockProvider {...props} childrenColumnName={childrenColumnName} params={paramsWithFilter} />

View File

@ -2,7 +2,7 @@ import { ArrayField } from '@formily/core';
import { Schema, useField, useFieldSchema } from '@formily/react'; import { Schema, useField, useFieldSchema } from '@formily/react';
import _ from 'lodash'; import _ from 'lodash';
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
import React, { createContext, useContext, useEffect, useState } from 'react'; import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { useCollectionManager_deprecated } from '../collection-manager'; import { useCollectionManager_deprecated } from '../collection-manager';
import { useCollectionParentRecordData } from '../data-source/collection-record/CollectionRecordProvider'; import { useCollectionParentRecordData } from '../data-source/collection-record/CollectionRecordProvider';
import { isInFilterFormBlock } from '../filter-provider'; import { isInFilterFormBlock } from '../filter-provider';
@ -153,6 +153,8 @@ export const TableSelectorProvider = (props: TableSelectorProviderProps) => {
const collection = getCollection(collectionField?.collectionName); const collection = getCollection(collectionField?.collectionName);
const primaryKey = collection?.getPrimaryKey(); const primaryKey = collection?.getPrimaryKey();
const appends = useAssociationNames(props.collection); const appends = useAssociationNames(props.collection);
const scope = useMemo(() => ({ treeTable }), [treeTable]);
let params = { ...props.params }; let params = { ...props.params };
if (props.dragSort) { if (props.dragSort) {
params['sort'] = ['sort']; params['sort'] = ['sort'];
@ -255,9 +257,8 @@ export const TableSelectorProvider = (props: TableSelectorProviderProps) => {
if (params?.filter) { if (params?.filter) {
params.filter = parsedFilter; params.filter = parsedFilter;
} }
return ( return (
<SchemaComponentOptions scope={{ treeTable }}> <SchemaComponentOptions scope={scope}>
<BlockProvider name="table-selector" {...props} params={params}> <BlockProvider name="table-selector" {...props} params={params}>
<InternalTableSelectorProvider {...props} params={params} extraFilter={extraFilter} /> <InternalTableSelectorProvider {...props} params={params} extraFilter={extraFilter} />
</BlockProvider> </BlockProvider>

View File

@ -1,7 +1,7 @@
import { ArrayField } from '@formily/core'; import { ArrayField } from '@formily/core';
import { useField } from '@formily/react'; import { useField } from '@formily/react';
import { error } from '@nocobase/utils/client'; import { error } from '@nocobase/utils/client';
import React from 'react'; import React, { useMemo } from 'react';
import { SchemaComponentOptions } from '..'; import { SchemaComponentOptions } from '..';
import { useAPIClient, useRequest } from '../api-client'; import { useAPIClient, useRequest } from '../api-client';
@ -74,8 +74,9 @@ const useChinaRegionLoadData = () => {
}; };
export const ChinaRegionProvider = (props) => { export const ChinaRegionProvider = (props) => {
const scope = useMemo(() => ({ useChinaRegionDataSource, useChinaRegionLoadData }), []);
return ( return (
<SchemaComponentOptions scope={{ useChinaRegionDataSource, useChinaRegionLoadData }}> <SchemaComponentOptions scope={scope}>
{props.children} {props.children}
</SchemaComponentOptions> </SchemaComponentOptions>
); );

View File

@ -180,6 +180,9 @@ export const AddCollectionAction = (props) => {
}; };
}, [category, items]); }, [category, items]);
const components = useMemo(() => ({ ...components, ArrayTable, TemplateSummary }), []);
const scopes = useMemo(() => ({ getContainer, useCancelAction, useCreateCollection, record, ...scope }), [scope]);
return ( return (
<RecordProvider record={record}> <RecordProvider record={record}>
<ActionContextProvider value={{ visible, setVisible }}> <ActionContextProvider value={{ visible, setVisible }}>
@ -192,16 +195,8 @@ export const AddCollectionAction = (props) => {
</Dropdown> </Dropdown>
<SchemaComponent <SchemaComponent
schema={schema} schema={schema}
components={{ ...components, ArrayTable, TemplateSummay: TemplateSummary }} components={components}
scope={{ scope={scopes}
getContainer,
useCancelAction,
createOnly: true,
useCreateCollection,
record,
showReverseFieldConfig: true,
...scope,
}}
/> />
</ActionContextProvider> </ActionContextProvider>
</RecordProvider> </RecordProvider>

View File

@ -199,12 +199,19 @@ const MenuEditor = (props) => {
}, },
); );
const scope = useMemo(() => ({
useMenuProps,
onSelect,
sideMenuRef,
defaultSelectedUid
}), [])
if (loading) { if (loading) {
return render(); return render();
} }
return ( return (
<SchemaIdContext.Provider value={defaultSelectedUid}> <SchemaIdContext.Provider value={defaultSelectedUid}>
<SchemaComponent memoized scope={{ useMenuProps, onSelect, sideMenuRef, defaultSelectedUid }} schema={schema} /> <SchemaComponent memoized scope={scope} schema={schema} />
</SchemaIdContext.Provider> </SchemaIdContext.Provider>
); );
}; };

View File

@ -29,6 +29,22 @@ import { useTableSelectorProps } from './InternalPicker';
import { getLabelFormatValue, useLabelUiSchema } from './util'; import { getLabelFormatValue, useLabelUiSchema } from './util';
import { markRecordAsNew } from '../../../data-source/collection-record/isNewRecord'; import { markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
const usePickActionProps = () => {
const { setVisible } = useActionContext();
const { selectedRows, options, collectionField } = useContext(RecordPickerContext);
const { field } = useAssociationFieldContext<ArrayField>();
return {
onClick() {
const selectData = unionBy(selectedRows, options, collectionField?.targetKey || 'id');
const data = field.value || [];
field.value = uniqBy(data.concat(selectData), collectionField?.targetKey || 'id');
field.onInput(field.value);
setVisible(false);
},
};
};
export const SubTable: any = observer( export const SubTable: any = observer(
(props: any) => { (props: any) => {
const { openSize } = props; const { openSize } = props;
@ -88,25 +104,19 @@ export const SubTable: any = observer(
setSelectedRows, setSelectedRows,
collectionField, collectionField,
}; };
const usePickActionProps = () => {
const { setVisible } = useActionContext();
const { selectedRows, options, collectionField } = useContext(RecordPickerContext);
return {
onClick() {
const selectData = unionBy(selectedRows, options, collectionField?.targetKey || 'id');
const data = field.value || [];
field.value = uniqBy(data.concat(selectData), collectionField?.targetKey || 'id');
field.onInput(field.value);
setVisible(false);
},
};
};
const getFilter = () => { const getFilter = () => {
const targetKey = collectionField?.targetKey || 'id'; const targetKey = collectionField?.targetKey || 'id';
const list = options.map((option) => option[targetKey]).filter(Boolean); const list = options.map((option) => option[targetKey]).filter(Boolean);
const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {}; const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {};
return filter; return filter;
}; };
const scope = useMemo(() => ({
usePickActionProps,
useTableSelectorProps,
useCreateActionProps,
}), []);
return ( return (
<div <div
className={css` className={css`
@ -217,11 +227,7 @@ export const SubTable: any = observer(
<FormProvider> <FormProvider>
<TableSelectorParamsProvider params={{ filter: getFilter() }}> <TableSelectorParamsProvider params={{ filter: getFilter() }}>
<SchemaComponentOptions <SchemaComponentOptions
scope={{ scope={scope}
usePickActionProps,
useTableSelectorProps,
useCreateActionProps,
}}
> >
<RecursionField <RecursionField
onlyRenderProperties onlyRenderProperties

View File

@ -2,7 +2,7 @@ import { css, cx } from '@emotion/css';
import { ArrayField } from '@formily/core'; import { ArrayField } from '@formily/core';
import { RecursionField, Schema, useField, useFieldSchema } from '@formily/react'; import { RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
import { List as AntdList, Col, PaginationProps } from 'antd'; import { List as AntdList, Col, PaginationProps } from 'antd';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { SortableItem } from '../../common'; import { SortableItem } from '../../common';
import { SchemaComponentOptions } from '../../core'; import { SchemaComponentOptions } from '../../core';
import { useDesigner, useProps } from '../../hooks'; import { useDesigner, useProps } from '../../hooks';
@ -96,12 +96,14 @@ const InternalGridCard = (props) => {
[run, params], [run, params],
); );
const scope = useMemo(() => ({
useGridCardItemProps,
useGridCardActionBarProps,
}), [])
return ( return (
<SchemaComponentOptions <SchemaComponentOptions
scope={{ scope={scope}
useGridCardItemProps,
useGridCardActionBarProps,
}}
> >
<SortableItem className={cx('nb-card-list', designerCss)}> <SortableItem className={cx('nb-card-list', designerCss)}>
<AntdList <AntdList

View File

@ -1,5 +1,5 @@
import { IRecursionFieldProps, ISchemaFieldProps, RecursionField, Schema } from '@formily/react'; import { IRecursionFieldProps, ISchemaFieldProps, RecursionField, Schema } from '@formily/react';
import React, { useContext, useMemo } from 'react'; import React, { memo, useCallback, useContext, useMemo } from 'react';
import { SchemaComponentContext } from '../context'; import { SchemaComponentContext } from '../context';
import { SchemaComponentOptions } from './SchemaComponentOptions'; import { SchemaComponentOptions } from './SchemaComponentOptions';
@ -30,15 +30,19 @@ const RecursionSchemaComponent = (props: ISchemaFieldProps & SchemaComponentOnCh
const { components, scope, schema, ...others } = props; const { components, scope, schema, ...others } = props;
const ctx = useContext(SchemaComponentContext); const ctx = useContext(SchemaComponentContext);
const s = useMemo(() => toSchema(schema), [schema]); const s = useMemo(() => toSchema(schema), [schema]);
const refresh = useCallback(() => {
ctx.refresh?.();
props.onChange?.(s);
}, [s, ctx.refresh, props.onChange]);
const contextValue = useMemo(() => ({
...ctx,
refresh
}), [ctx, refresh]);
return ( return (
<SchemaComponentContext.Provider <SchemaComponentContext.Provider
value={{ value={contextValue}
...ctx,
refresh: () => {
ctx.refresh?.();
props.onChange?.(s);
},
}}
> >
<SchemaComponentOptions inherit components={components} scope={scope}> <SchemaComponentOptions inherit components={components} scope={scope}>
<RecursionField {...others} schema={s} /> <RecursionField {...others} schema={s} />
@ -53,7 +57,7 @@ const MemoizedSchemaComponent = (props: ISchemaFieldProps & SchemaComponentOnCha
return <RecursionSchemaComponent {...others} schema={s} />; return <RecursionSchemaComponent {...others} schema={s} />;
}; };
export const SchemaComponent = ( export const SchemaComponent = memo((
props: (ISchemaFieldProps | IRecursionFieldProps) & { memoized?: boolean } & SchemaComponentOnChange, props: (ISchemaFieldProps | IRecursionFieldProps) & { memoized?: boolean } & SchemaComponentOnChange,
) => { ) => {
const { memoized, ...others } = props; const { memoized, ...others } = props;
@ -61,4 +65,6 @@ export const SchemaComponent = (
return <MemoizedSchemaComponent {...others} />; return <MemoizedSchemaComponent {...others} />;
} }
return <RecursionSchemaComponent {...others} />; return <RecursionSchemaComponent {...others} />;
}; });
SchemaComponent.displayName = 'SchemaComponent';

View File

@ -1,7 +1,7 @@
import { createForm } from '@formily/core'; import { createForm } from '@formily/core';
import { FormProvider, Schema } from '@formily/react'; import { FormProvider, Schema } from '@formily/react';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import React, { useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SchemaComponentContext } from '../context'; import { SchemaComponentContext } from '../context';
import { ISchemaComponentProvider } from '../types'; import { ISchemaComponentProvider } from '../types';
@ -53,27 +53,35 @@ export const SchemaComponentProvider: React.FC<ISchemaComponentProvider> = (prop
}, [props.scope, t]); }, [props.scope, t]);
const [active, setActive] = useState(designable); const [active, setActive] = useState(designable);
const schemaComponentContextValue = useMemo( const refresh = useCallback(() => {
() => ({ setUid(uid());
}, []);
const reset = useCallback(() => {
setFormId(uid());
}, []);
const setDesignable = useCallback((value) => {
if (typeof designable !== 'boolean') {
setActive(value);
}
onDesignableChange?.(value);
}, [designable, onDesignableChange]);
const contextValue = useMemo(() => {
return {
scope, scope,
components, components,
reset: () => setFormId(uid()), reset,
refresh: () => { refresh,
setUid(uid());
},
designable: typeof designable === 'boolean' ? designable : active, designable: typeof designable === 'boolean' ? designable : active,
setDesignable: (value) => { setDesignable,
if (typeof designable !== 'boolean') { };
setActive(value);
} // uidValue 虽然没用到,但是这里必须加上,为了让整个页面能够渲染
onDesignableChange?.(value); }, [uidValue, scope, components, reset, refresh, designable, active, setDesignable])
},
}),
[uidValue, scope, components, designable, active],
);
return ( return (
<SchemaComponentContext.Provider value={schemaComponentContextValue}> <SchemaComponentContext.Provider value={contextValue}>
<FormProvider form={form}> <FormProvider form={form}>
<SchemaComponentOptions inherit scope={scope} components={components}> <SchemaComponentOptions inherit scope={scope} components={components}>
{children} {children}

View File

@ -2,7 +2,7 @@ import { ISchema, useForm } from '@formily/react';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import { Card, message } from 'antd'; import { Card, message } from 'antd';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import React from 'react'; import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSystemSettings } from '.'; import { useSystemSettings } from '.';
import { i18n, useAPIClient, useRequest } from '..'; import { i18n, useAPIClient, useRequest } from '..';
@ -164,10 +164,11 @@ const schema: ISchema = {
}; };
export const SystemSettingsPane = () => { export const SystemSettingsPane = () => {
const scope = useMemo(() => ({ useSaveSystemSettingsValues, useSystemSettingsValues, useCloseAction }), []);
return ( return (
<Card bordered={false}> <Card bordered={false}>
<SchemaComponent <SchemaComponent
scope={{ useSaveSystemSettingsValues, useSystemSettingsValues, useCloseAction }} scope={scope}
schema={schema} schema={schema}
/> />
</Card> </Card>

View File

@ -1,5 +1,5 @@
import { SchemaComponentOptions } from '@nocobase/client'; import { SchemaComponentOptions } from '@nocobase/client';
import React, { useState } from 'react'; import React, { memo, useMemo, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { ImportActionInitializer, ImportDesigner } from '.'; import { ImportActionInitializer, ImportDesigner } from '.';
import { ImportContext } from './context'; import { ImportContext } from './context';
@ -9,23 +9,30 @@ import { useShared } from './useShared';
export const ImportPluginProvider = (props: any) => { export const ImportPluginProvider = (props: any) => {
const { uploadValidator, beforeUploadHandler, validateUpload } = useShared(); const { uploadValidator, beforeUploadHandler, validateUpload } = useShared();
const scope = useMemo(() => ({
uploadValidator,
validateUpload,
beforeUploadHandler,
useDownloadXlsxTemplateAction,
useImportStartAction,
}), [uploadValidator, beforeUploadHandler, validateUpload])
const components = useMemo(() => ({
ImportActionInitializer,
ImportDesigner,
}), []);
return ( return (
<SchemaComponentOptions <SchemaComponentOptions
components={{ ImportActionInitializer, ImportDesigner }} components={components}
scope={{ scope={scope}
uploadValidator,
validateUpload,
beforeUploadHandler,
useDownloadXlsxTemplateAction,
useImportStartAction,
}}
> >
<ImportContextProvider>{props.children}</ImportContextProvider> <ImportContextProvider>{props.children}</ImportContextProvider>
</SchemaComponentOptions> </SchemaComponentOptions>
); );
}; };
export const ImportContextProvider = (props: any) => { export const ImportContextProvider = memo((props: any) => {
const [importModalVisible, setImportModalVisible] = useState(false); const [importModalVisible, setImportModalVisible] = useState(false);
const [importStatus, setImportStatus] = useState<number>(ImportStatus.IMPORTING); const [importStatus, setImportStatus] = useState<number>(ImportStatus.IMPORTING);
const [importResult, setImportResult] = useState<{ const [importResult, setImportResult] = useState<{
@ -47,4 +54,4 @@ export const ImportContextProvider = (props: any) => {
{props.children} {props.children}
</ImportContext.Provider> </ImportContext.Provider>
); );
}; });