From b3a4201a82617534b9f5c3d16d4769f1327b3b02 Mon Sep 17 00:00:00 2001 From: Zeke Zhang <958414905@qq.com> Date: Sun, 3 Nov 2024 06:11:05 +0800 Subject: [PATCH] Revert "perf(CollectionField): use custom RecursionField component to avoid unnecessary re-renders" This reverts commit 203ecc1334429a8b77177337c8649ece1abdaeed. --- .../collection-field/CollectionField.tsx | 74 +++++++-- .../src/formily/NocoBaseRecursionField.tsx | 148 ------------------ .../schema-component/antd/table-v2/Table.tsx | 27 +--- 3 files changed, 71 insertions(+), 178 deletions(-) delete mode 100644 packages/core/client/src/formily/NocoBaseRecursionField.tsx diff --git a/packages/core/client/src/data-source/collection-field/CollectionField.tsx b/packages/core/client/src/data-source/collection-field/CollectionField.tsx index 1ddc895a04..12cde9197d 100644 --- a/packages/core/client/src/data-source/collection-field/CollectionField.tsx +++ b/packages/core/client/src/data-source/collection-field/CollectionField.tsx @@ -7,11 +7,14 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { connect, useFieldSchema } from '@formily/react'; -import React from 'react'; +import { Field } from '@formily/core'; +import { connect, Schema, useField, useFieldSchema } from '@formily/react'; +import { merge } from '@formily/shared'; +import { concat } from 'lodash'; +import React, { useEffect } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { useDynamicComponentProps } from '../../hoc/withDynamicSchemaProps'; -import { ErrorFallback, useComponent } from '../../schema-component'; +import { ErrorFallback, useCompile, useComponent } from '../../schema-component'; import { CollectionFieldProvider, useCollectionField } from './CollectionFieldProvider'; type Props = { @@ -19,15 +22,64 @@ type Props = { children?: React.ReactNode; }; -export const CollectionFieldInternalField: React.FC = (props: Props) => { - const fieldSchema = useFieldSchema(); - const { uiSchema } = useCollectionField(); - const Component = useComponent( - fieldSchema['x-component-props']?.['component'] || uiSchema?.['x-component'] || 'Input', - ); - const dynamicProps = useDynamicComponentProps(uiSchema?.['x-use-component-props'], props); +const setFieldProps = (field: Field, key: string, value: any) => { + if (field[key] === undefined) { + field[key] = value; + } +}; - if (!uiSchema) return null; +const setRequired = (field: Field, fieldSchema: Schema, uiSchema: Schema) => { + if (typeof fieldSchema['required'] === 'undefined') { + field.required = !!uiSchema['required']; + } +}; + +/** + * TODO: 初步适配 + * @internal + */ +export const CollectionFieldInternalField: React.FC = (props: Props) => { + const compile = useCompile(); + const field = useField(); + const fieldSchema = useFieldSchema(); + const { uiSchema: uiSchemaOrigin, defaultValue } = useCollectionField(); + const Component = useComponent( + fieldSchema['x-component-props']?.['component'] || uiSchemaOrigin?.['x-component'] || 'Input', + ); + const dynamicProps = useDynamicComponentProps(uiSchemaOrigin?.['x-use-component-props'], props); + + // TODO: 初步适配 + useEffect(() => { + if (!uiSchemaOrigin) { + return; + } + const uiSchema = compile(uiSchemaOrigin); + setFieldProps(field, 'content', uiSchema['x-content']); + setFieldProps(field, 'title', uiSchema.title); + setFieldProps(field, 'description', uiSchema.description); + + if (fieldSchema.default == null && defaultValue != null) { + setFieldProps(field, 'initialValue', defaultValue); + } + + if (!field.validator && (uiSchema['x-validator'] || fieldSchema['x-validator'])) { + const concatSchema = concat([], uiSchema['x-validator'] || [], fieldSchema['x-validator'] || []); + field.validator = concatSchema; + } + if (fieldSchema['x-disabled'] === true) { + field.disabled = true; + } + if (fieldSchema['x-read-pretty'] === true) { + field.readPretty = true; + } + setRequired(field, fieldSchema, uiSchema); + // @ts-ignore + field.dataSource = uiSchema.enum; + const originalProps = uiSchema['x-component-props'] || {}; + field.componentProps = merge(originalProps, field.componentProps || {}, dynamicProps || {}); + }, [uiSchemaOrigin]); + + if (!uiSchemaOrigin) return null; return ; }; diff --git a/packages/core/client/src/formily/NocoBaseRecursionField.tsx b/packages/core/client/src/formily/NocoBaseRecursionField.tsx deleted file mode 100644 index bfe3b6d224..0000000000 --- a/packages/core/client/src/formily/NocoBaseRecursionField.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import { GeneralField } from '@formily/core'; -import { - ArrayField, - Field, - IRecursionFieldProps, - ISchema, - ObjectField, - ReactFC, - Schema, - SchemaContext, - useExpressionScope, - useField, - VoidField, -} from '@formily/react'; -import { isBool, isFn, isValid, merge } from '@formily/shared'; -import _ from 'lodash'; -import React, { Fragment, useMemo } from 'react'; - -interface INocoBaseRecursionFieldProps extends IRecursionFieldProps { - /** - * Default Schema for collection fields - */ - uiSchema?: ISchema; -} - -const useFieldProps = (schema: Schema) => { - const scope = useExpressionScope(); - return schema.toFieldProps({ - scope, - }) as any; -}; - -const useBasePath = (props: IRecursionFieldProps) => { - const parent = useField(); - if (props.onlyRenderProperties) { - return props.basePath || parent?.address.concat(props.name); - } - return props.basePath || parent?.address; -}; - -/** - * Based on @formily/react v2.3.2 RecursionField component - * Modified to better adapt to NocoBase's needs - */ -export const NocoBaseRecursionField: ReactFC = React.memo((props) => { - const basePath = useBasePath(props); - const fieldSchema = useMemo(() => new Schema(props.schema), [props.schema]); - - // Merge default Schema of collection fields - const mergedFieldSchema = useMemo(() => { - if (props.uiSchema) { - const clonedSchema = _.cloneDeep(props.schema); - if (props.onlyRenderProperties) { - if (!clonedSchema.properties) { - throw new Error('[NocoBaseRecursionField]: properties is required'); - } - - const firstPropertyKey = Object.keys(clonedSchema.properties)[0]; - const firstPropertyValue = Object.values(clonedSchema.properties)[0]; - clonedSchema.properties[firstPropertyKey] = merge(props.uiSchema, firstPropertyValue); - return new Schema(clonedSchema); - } - return new Schema(merge(props.uiSchema, clonedSchema)); - } - - return fieldSchema; - }, [fieldSchema, props.onlyRenderProperties, props.schema, props.uiSchema]); - - const fieldProps = useFieldProps(mergedFieldSchema); - const renderProperties = (field?: GeneralField) => { - if (props.onlyRenderSelf) return; - const properties = Schema.getOrderProperties(mergedFieldSchema); - if (!properties.length) return; - return ( - - {properties.map(({ schema: item, key: name }, index) => { - const base = field?.address || basePath; - let schema: Schema = item; - if (isFn(props.mapProperties)) { - const mapped = props.mapProperties(item, name); - if (mapped) { - schema = mapped; - } - } - if (isFn(props.filterProperties)) { - if (props.filterProperties(schema, name) === false) { - return null; - } - } - if (isBool(props.propsRecursion) && props.propsRecursion) { - return ( - - ); - } - return ; - })} - - ); - }; - - const render = () => { - if (!isValid(props.name)) return renderProperties(); - if (mergedFieldSchema.type === 'object') { - if (props.onlyRenderProperties) return renderProperties(); - return ( - - {renderProperties} - - ); - } else if (mergedFieldSchema.type === 'array') { - return ; - } else if (mergedFieldSchema.type === 'void') { - if (props.onlyRenderProperties) return renderProperties(); - return ( - - {renderProperties} - - ); - } - return ; - }; - - if (!fieldSchema) return ; - - // The original fieldSchema is still passed down to maintain compatibility with NocoBase usage. - // fieldSchema stores some user-defined content. If we pass down mergedFieldSchema instead, - // some default schema values would also be saved in fieldSchema. - return {render()}; -}); - -NocoBaseRecursionField.displayName = 'NocoBaseRecursionField'; diff --git a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx index 2ae88a0eb0..3cc0eee120 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx @@ -13,7 +13,7 @@ import { SortableContext, SortableContextProps, useSortable } from '@dnd-kit/sor import { css, cx } from '@emotion/css'; import { ArrayField } from '@formily/core'; import { spliceArrayState } from '@formily/core/esm/shared/internals'; -import { Schema, observer, useField, useFieldSchema } from '@formily/react'; +import { RecursionField, Schema, observer, useField, useFieldSchema } from '@formily/react'; import { action, raw } from '@formily/reactive'; import { uid } from '@formily/shared'; import { isPortalInBody } from '@nocobase/utils/client'; @@ -38,7 +38,6 @@ import { import { useACLFieldWhitelist } from '../../../acl/ACLProvider'; import { useTableBlockContext } from '../../../block-provider/TableBlockProvider'; import { isNewRecord } from '../../../data-source/collection-record/isNewRecord'; -import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField'; import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps'; import { withSkeletonComponent } from '../../../hoc/withSkeletonComponent'; import { useSatisfiedActionValues } from '../../../schema-settings/LinkageRules/useActionValues'; @@ -48,6 +47,7 @@ import { ColumnFieldProvider } from './components/ColumnFieldProvider'; import { TableSkeleton } from './TableSkeleton'; import { extractIndex, isCollectionFieldComponent, isColumnComponent } from './utils'; +const RecursionFieldMemo = React.memo(RecursionField); const InViewContext = React.createContext(false); const useArrayField = (props) => { @@ -95,14 +95,14 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat const { designable } = useDesignable(); const { exists, render } = useSchemaInitializerRender(schema['x-initializer'], schema['x-initializer-props']); const parentRecordData = useCollectionParentRecordData(); - const columnsSchemas = schema.reduceProperties((buf, s) => { + const columnsSchema = schema.reduceProperties((buf, s) => { if (isColumnComponent(s) && schemaInWhitelist(Object.values(s.properties || {}).pop())) { return buf.concat([s]); } return buf; }, []); const { current, pageSize } = paginationProps; - const hasChangedColumns = useColumnsDeepMemoized(columnsSchemas); + const hasChangedColumns = useColumnsDeepMemoized(columnsSchema); const schemaToolbarBigger = useMemo(() => { return css` @@ -111,27 +111,21 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat padding: ${token.paddingContentVerticalLG}px ${token.margin}px; } `; - }, [token.paddingContentVerticalLG, token.marginSM, token.margin]); + }, [token.paddingContentVerticalLG, token.marginSM]); const collection = useCollection(); const columns = useMemo( () => - columnsSchemas?.map((columnSchema: Schema) => { + columnsSchema?.map((columnSchema: Schema) => { const collectionFields = columnSchema.reduceProperties((buf, s) => { if (isCollectionFieldComponent(s)) { return buf.concat([s]); } }, []); const dataIndex = collectionFields?.length > 0 ? collectionFields[0].name : columnSchema.name; - const { uiSchema, defaultValue } = collection?.getField(dataIndex) || {}; - - if (uiSchema) { - uiSchema.default = defaultValue; - } - return { - title: , + title: , dataIndex, key: columnSchema.name, sorter: columnSchema['x-component-props']?.['sorter'], @@ -145,12 +139,7 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat - +