mirror of
https://github.com/nocobase/nocobase
synced 2024-11-16 03:55:10 +00:00
perf(PageTabs): cache rendered content to prevent re-rendering
This commit is contained in:
parent
79472624b5
commit
f6de620be5
@ -14,7 +14,7 @@ import { FormLayout } from '@formily/antd-v5';
|
|||||||
import { Schema, SchemaOptionsContext, useFieldSchema } from '@formily/react';
|
import { Schema, SchemaOptionsContext, useFieldSchema } from '@formily/react';
|
||||||
import { Button, Tabs } from 'antd';
|
import { Button, Tabs } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
import React, { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { NavigateFunction, Outlet, useOutletContext, useParams, useSearchParams } from 'react-router-dom';
|
import { NavigateFunction, Outlet, useOutletContext, useParams, useSearchParams } from 'react-router-dom';
|
||||||
@ -42,7 +42,7 @@ export const Page = (props) => {
|
|||||||
const dn = useDesignable();
|
const dn = useDesignable();
|
||||||
const { theme } = useGlobalTheme();
|
const { theme } = useGlobalTheme();
|
||||||
const { getAriaLabel } = useGetAriaLabelOfSchemaInitializer();
|
const { getAriaLabel } = useGetAriaLabelOfSchemaInitializer();
|
||||||
const { tabUid, name: pageUid } = useParams();
|
const { tabUid } = useParams();
|
||||||
const basenameOfCurrentRouter = useRouterBasename();
|
const basenameOfCurrentRouter = useRouterBasename();
|
||||||
|
|
||||||
const disablePageHeader = fieldSchema['x-component-props']?.disablePageHeader;
|
const disablePageHeader = fieldSchema['x-component-props']?.disablePageHeader;
|
||||||
@ -63,23 +63,26 @@ export const Page = (props) => {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const footer = useMemo(() => {
|
|
||||||
return enablePageTabs ? (
|
|
||||||
<DndContext>
|
|
||||||
<Tabs
|
|
||||||
size={'small'}
|
|
||||||
activeKey={activeKey}
|
|
||||||
// 这里的样式是为了保证页面 tabs 标签下面的分割线和页面内容对齐(页面内边距可以通过主题编辑器调节)
|
// 这里的样式是为了保证页面 tabs 标签下面的分割线和页面内容对齐(页面内边距可以通过主题编辑器调节)
|
||||||
tabBarStyle={{
|
const tabBarStyle = useMemo(
|
||||||
|
() => ({
|
||||||
paddingLeft: token.paddingLG - token.paddingPageHorizontal,
|
paddingLeft: token.paddingLG - token.paddingPageHorizontal,
|
||||||
paddingRight: token.paddingLG - token.paddingPageHorizontal,
|
paddingRight: token.paddingLG - token.paddingPageHorizontal,
|
||||||
marginLeft: token.paddingPageHorizontal - token.paddingLG,
|
marginLeft: token.paddingPageHorizontal - token.paddingLG,
|
||||||
marginRight: token.paddingPageHorizontal - token.paddingLG,
|
marginRight: token.paddingPageHorizontal - token.paddingLG,
|
||||||
}}
|
}),
|
||||||
onChange={(activeKey) => {
|
[token.paddingLG, token.paddingPageHorizontal],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTabsChange = useCallback(
|
||||||
|
(activeKey: string): void => {
|
||||||
navigateToTab({ activeKey, navigate, basename: basenameOfCurrentRouter });
|
navigateToTab({ activeKey, navigate, basename: basenameOfCurrentRouter });
|
||||||
}}
|
},
|
||||||
tabBarExtraContent={
|
[basenameOfCurrentRouter, navigate],
|
||||||
|
);
|
||||||
|
|
||||||
|
const tabBarExtraContent = useMemo(() => {
|
||||||
|
return (
|
||||||
dn.designable && (
|
dn.designable && (
|
||||||
<Button
|
<Button
|
||||||
aria-label={getAriaLabel('tabs')}
|
aria-label={getAriaLabel('tabs')}
|
||||||
@ -132,8 +135,11 @@ export const Page = (props) => {
|
|||||||
{t('Add tab')}
|
{t('Add tab')}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
);
|
||||||
items={fieldSchema.mapProperties((schema) => {
|
}, [dn, getAriaLabel, options?.components, options?.scope, t, theme]);
|
||||||
|
|
||||||
|
const items = useMemo(() => {
|
||||||
|
return fieldSchema.mapProperties((schema) => {
|
||||||
return {
|
return {
|
||||||
label: (
|
label: (
|
||||||
<SortableItem
|
<SortableItem
|
||||||
@ -148,20 +154,23 @@ export const Page = (props) => {
|
|||||||
),
|
),
|
||||||
key: schema.name as string,
|
key: schema.name as string,
|
||||||
};
|
};
|
||||||
})}
|
});
|
||||||
|
}, [fieldSchema, props.className, t]);
|
||||||
|
|
||||||
|
const footer = useMemo(() => {
|
||||||
|
return enablePageTabs ? (
|
||||||
|
<DndContext>
|
||||||
|
<Tabs
|
||||||
|
size={'small'}
|
||||||
|
activeKey={activeKey}
|
||||||
|
tabBarStyle={tabBarStyle}
|
||||||
|
onChange={handleTabsChange}
|
||||||
|
tabBarExtraContent={tabBarExtraContent}
|
||||||
|
items={items}
|
||||||
/>
|
/>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
) : null;
|
) : null;
|
||||||
}, [
|
}, [activeKey, enablePageTabs, handleTabsChange, items, tabBarExtraContent, tabBarStyle]);
|
||||||
activeKey,
|
|
||||||
fieldSchema,
|
|
||||||
dn.designable,
|
|
||||||
options.scope,
|
|
||||||
options.components,
|
|
||||||
pageUid,
|
|
||||||
fieldSchema.mapProperties((schema) => schema.title || t('Unnamed')).join(),
|
|
||||||
enablePageTabs,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return wrapSSR(
|
return wrapSSR(
|
||||||
<div className={`${componentCls} ${hashId} ${antTableCell}`}>
|
<div className={`${componentCls} ${hashId} ${antTableCell}`}>
|
||||||
@ -217,6 +226,35 @@ const className1 = css`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Add a TabPane component to manage caching, implementing an effect similar to Vue's keep-alive
|
||||||
|
const TabPane = React.memo(({ schema, active }: { schema: Schema; active: boolean }) => {
|
||||||
|
const mountedRef = useRef(false);
|
||||||
|
|
||||||
|
if (active && !mountedRef.current) {
|
||||||
|
mountedRef.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSchema = useMemo(
|
||||||
|
() =>
|
||||||
|
new Schema({
|
||||||
|
properties: {
|
||||||
|
[schema.name]: schema,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[schema],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!mountedRef.current) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: active ? 'block' : 'none' }}>
|
||||||
|
<SchemaComponent distributed schema={newSchema} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const PageContent = memo(
|
const PageContent = memo(
|
||||||
({
|
({
|
||||||
loading,
|
loading,
|
||||||
@ -231,44 +269,23 @@ const PageContent = memo(
|
|||||||
fieldSchema: Schema<any, any, any, any, any, any, any, any, any>;
|
fieldSchema: Schema<any, any, any, any, any, any, any, any, any>;
|
||||||
activeKey: string;
|
activeKey: string;
|
||||||
}) => {
|
}) => {
|
||||||
// const { render } = useAppSpin();
|
|
||||||
|
|
||||||
// if (loading) {
|
|
||||||
// return render();
|
|
||||||
// }g
|
|
||||||
|
|
||||||
if (!disablePageHeader && enablePageTabs) {
|
if (!disablePageHeader && enablePageTabs) {
|
||||||
const result = fieldSchema
|
|
||||||
.mapProperties((schema) => {
|
|
||||||
if (schema.name !== activeKey) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaComponent
|
<>
|
||||||
key={schema.name}
|
{fieldSchema.mapProperties((schema) => (
|
||||||
distributed
|
<TabPane key={schema.name} schema={schema} active={schema.name === activeKey} />
|
||||||
schema={
|
))}
|
||||||
new Schema({
|
</>
|
||||||
properties: {
|
|
||||||
[schema.name]: schema,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
})
|
}
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
return result[0];
|
|
||||||
} else {
|
|
||||||
return (
|
return (
|
||||||
<div className={className1}>
|
<div className={className1}>
|
||||||
<SchemaComponent schema={fieldSchema} distributed />
|
<SchemaComponent schema={fieldSchema} distributed />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
PageContent.displayName = 'PageContent';
|
|
||||||
|
|
||||||
function NocoBasePageHeader({ footer }: { footer: React.JSX.Element }) {
|
function NocoBasePageHeader({ footer }: { footer: React.JSX.Element }) {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
Loading…
Reference in New Issue
Block a user