perf(MenuEditor): optimize router context

This commit is contained in:
Zeke Zhang 2024-11-03 18:16:06 +08:00
parent d786145fc2
commit 023aa0ce4f
2 changed files with 74 additions and 16 deletions

View File

@ -8,7 +8,16 @@
*/ */
import React, { FC, useEffect } from 'react'; import React, { FC, useEffect } from 'react';
import { Location, NavigateFunction, NavigateOptions, useLocation, useNavigate } from 'react-router-dom'; import {
Location,
NavigateFunction,
NavigateOptions,
PathMatch,
useLocation,
useMatch,
useNavigate,
useParams,
} from 'react-router-dom';
const NavigateNoUpdateContext = React.createContext<NavigateFunction>(null); const NavigateNoUpdateContext = React.createContext<NavigateFunction>(null);
NavigateNoUpdateContext.displayName = 'NavigateNoUpdateContext'; NavigateNoUpdateContext.displayName = 'NavigateNoUpdateContext';
@ -22,12 +31,36 @@ LocationSearchContext.displayName = 'LocationSearchContext';
const IsAdminPageContext = React.createContext<boolean>(false); const IsAdminPageContext = React.createContext<boolean>(false);
IsAdminPageContext.displayName = 'IsAdminPageContext'; IsAdminPageContext.displayName = 'IsAdminPageContext';
const CurrentPageUidContext = React.createContext<string>('');
CurrentPageUidContext.displayName = 'CurrentPageUidContext';
const MatchAdminContext = React.createContext<PathMatch<string> | null>(null);
MatchAdminContext.displayName = 'MatchAdminContext';
const MatchAdminNameContext = React.createContext<PathMatch<string> | null>(null);
MatchAdminNameContext.displayName = 'MatchAdminNameContext';
const MatchAdminProvider: FC = ({ children }) => {
const matchAdmin = useMatch('/admin');
return <MatchAdminContext.Provider value={matchAdmin}>{children}</MatchAdminContext.Provider>;
};
const MatchAdminNameProvider: FC = ({ children }) => {
const matchAdminName = useMatch('/admin/:name');
return <MatchAdminNameContext.Provider value={matchAdminName}>{children}</MatchAdminNameContext.Provider>;
};
const IsAdminPageProvider: FC = ({ children }) => { const IsAdminPageProvider: FC = ({ children }) => {
const location = useLocation(); const location = useLocation();
const isAdminPage = location.pathname.startsWith('/admin'); const isAdminPage = location.pathname.startsWith('/admin');
return <IsAdminPageContext.Provider value={isAdminPage}>{children}</IsAdminPageContext.Provider>; return <IsAdminPageContext.Provider value={isAdminPage}>{children}</IsAdminPageContext.Provider>;
}; };
const CurrentPageUidProvider: FC = ({ children }) => {
const params = useParams<any>();
return <CurrentPageUidContext.Provider value={params.name}>{children}</CurrentPageUidContext.Provider>;
};
/** /**
* When the URL changes, components that use `useNavigate` will re-render. * When the URL changes, components that use `useNavigate` will re-render.
* This provider provides a `navigateNoUpdate` method that can avoid re-rendering. * This provider provides a `navigateNoUpdate` method that can avoid re-rendering.
@ -96,12 +129,30 @@ export const useIsAdminPage = () => {
return React.useContext(IsAdminPageContext); return React.useContext(IsAdminPageContext);
}; };
export const useCurrentPageUid = () => {
return React.useContext(CurrentPageUidContext);
};
export const useMatchAdmin = () => {
return React.useContext(MatchAdminContext);
};
export const useMatchAdminName = () => {
return React.useContext(MatchAdminNameContext);
};
export const CustomRouterContextProvider: FC = ({ children }) => { export const CustomRouterContextProvider: FC = ({ children }) => {
return ( return (
<NavigateNoUpdateProvider> <NavigateNoUpdateProvider>
<LocationNoUpdateProvider> <LocationNoUpdateProvider>
<IsAdminPageProvider> <IsAdminPageProvider>
<LocationSearchProvider>{children}</LocationSearchProvider> <LocationSearchProvider>
<CurrentPageUidProvider>
<MatchAdminProvider>
<MatchAdminNameProvider>{children}</MatchAdminNameProvider>
</MatchAdminProvider>
</CurrentPageUidProvider>
</LocationSearchProvider>
</IsAdminPageProvider> </IsAdminPageProvider>
</LocationNoUpdateProvider> </LocationNoUpdateProvider>
</NavigateNoUpdateProvider> </NavigateNoUpdateProvider>

View File

@ -12,7 +12,7 @@ import { useSessionStorageState } from 'ahooks';
import { App, ConfigProvider, Divider, Layout } from 'antd'; import { App, ConfigProvider, Divider, Layout } from 'antd';
import { createGlobalStyle } from 'antd-style'; import { createGlobalStyle } from 'antd-style';
import React, { FC, createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import React, { FC, createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { Link, Outlet, useMatch, useParams } from 'react-router-dom'; import { Link, Outlet, useParams } from 'react-router-dom';
import { import {
ACLRolesCheckProvider, ACLRolesCheckProvider,
CurrentAppInfoProvider, CurrentAppInfoProvider,
@ -33,7 +33,13 @@ import {
useSystemSettings, useSystemSettings,
useToken, useToken,
} from '../../../'; } from '../../../';
import { useLocationNoUpdate, useNavigateNoUpdate } from '../../../application/CustomRouterContextProvider'; import {
useCurrentPageUid,
useLocationNoUpdate,
useMatchAdmin,
useMatchAdminName,
useNavigateNoUpdate,
} from '../../../application/CustomRouterContextProvider';
import { Plugin } from '../../../application/Plugin'; import { Plugin } from '../../../application/Plugin';
import { useAppSpin } from '../../../application/hooks/useAppSpin'; import { useAppSpin } from '../../../application/hooks/useAppSpin';
import { useMenuTranslation } from '../../../schema-component/antd/menu/locale'; import { useMenuTranslation } from '../../../schema-component/antd/menu/locale';
@ -82,12 +88,11 @@ const MenuEditor = (props) => {
const { setTitle: _setTitle } = useDocumentTitle(); const { setTitle: _setTitle } = useDocumentTitle();
const setTitle = useCallback((title) => _setTitle(t(title)), []); const setTitle = useCallback((title) => _setTitle(t(title)), []);
const navigate = useNavigateNoUpdate(); const navigate = useNavigateNoUpdate();
const params = useParams<any>();
const location = useLocationNoUpdate(); const location = useLocationNoUpdate();
const isMatchAdmin = useMatch('/admin'); const isMatchAdmin = useMatchAdmin();
const isMatchAdminName = useMatch('/admin/:name'); const isMatchAdminName = useMatchAdminName();
const defaultSelectedUid = params.name; const currentPageUid = useCurrentPageUid();
const isDynamicPage = !!defaultSelectedUid; const isDynamicPage = !!currentPageUid;
const { sideMenuRef } = props; const { sideMenuRef } = props;
const ctx = useACLRoleContext(); const ctx = useACLRoleContext();
const [current, setCurrent] = useState(null); const [current, setCurrent] = useState(null);
@ -126,7 +131,7 @@ const MenuEditor = (props) => {
if (!isMatchAdminName || !isDynamicPage) return; if (!isMatchAdminName || !isDynamicPage) return;
// url 为 `admin/xxx` 的情况 // url 为 `admin/xxx` 的情况
const s = findByUid(schema, defaultSelectedUid); const s = findByUid(schema, currentPageUid);
if (s) { if (s) {
setTitle(s.title); setTitle(s.title);
} else { } else {
@ -147,7 +152,9 @@ const MenuEditor = (props) => {
if (sideMenuRef.current) { if (sideMenuRef.current) {
const pageType = const pageType =
properties && properties &&
Object.values(properties).find((item) => item['x-uid'] === params.name && item['x-component'] === 'Menu.Item'); Object.values(properties).find(
(item) => item['x-uid'] === currentPageUid && item['x-component'] === 'Menu.Item',
);
const isSettingPage = location?.pathname.includes('/settings'); const isSettingPage = location?.pathname.includes('/settings');
if (pageType || isSettingPage) { if (pageType || isSettingPage) {
sideMenuRef.current.style.display = 'none'; sideMenuRef.current.style.display = 'none';
@ -155,7 +162,7 @@ const MenuEditor = (props) => {
sideMenuRef.current.style.display = 'block'; sideMenuRef.current.style.display = 'block';
} }
} }
}, [data?.data, params.name, sideMenuRef, location?.pathname]); }, [data?.data, currentPageUid, sideMenuRef, location?.pathname]);
const schema = useMemo(() => { const schema = useMemo(() => {
const s = filterByACL(data?.data, ctx); const s = filterByACL(data?.data, ctx);
@ -167,12 +174,12 @@ const MenuEditor = (props) => {
useEffect(() => { useEffect(() => {
if (isMatchAdminName) { if (isMatchAdminName) {
const s = findByUid(schema, defaultSelectedUid); const s = findByUid(schema, currentPageUid);
if (s) { if (s) {
setTitle(s.title); setTitle(s.title);
} }
} }
}, [defaultSelectedUid, isMatchAdmin, isMatchAdminName, schema, setTitle]); }, [currentPageUid, isMatchAdmin, isMatchAdminName, schema, setTitle]);
useRequest( useRequest(
{ {
@ -213,14 +220,14 @@ const MenuEditor = (props) => {
); );
const scope = useMemo(() => { const scope = useMemo(() => {
return { useMenuProps, onSelect, sideMenuRef, defaultSelectedUid }; return { useMenuProps, onSelect, sideMenuRef, defaultSelectedUid: currentPageUid };
}, []); }, []);
if (loading) { if (loading) {
return render(); return render();
} }
return ( return (
<SchemaIdContext.Provider value={defaultSelectedUid}> <SchemaIdContext.Provider value={currentPageUid}>
<SchemaComponent distributed scope={scope} schema={schema} /> <SchemaComponent distributed scope={scope} schema={schema} />
</SchemaIdContext.Provider> </SchemaIdContext.Provider>
); );