From 17c247af53f46759a06e7b44f8d94db63bf02fe9 Mon Sep 17 00:00:00 2001 From: EYHN Date: Tue, 12 Nov 2024 13:22:59 +0000 Subject: [PATCH] fix(core): speed up navigation performance (#8794) add some memo... --- .../src/components/page-list/page-group.tsx | 8 ++- .../pure/workspace-mode-filter-tab/index.tsx | 15 ++-- .../src/components/root-app-sidebar/index.tsx | 71 ++++++++----------- .../views/add-page-button/index.tsx | 29 +++++--- .../src/modules/explorer/views/tree/node.tsx | 47 ++++++++---- .../core/src/modules/workbench/index.ts | 1 + 6 files changed, 97 insertions(+), 74 deletions(-) diff --git a/packages/frontend/core/src/components/page-list/page-group.tsx b/packages/frontend/core/src/components/page-list/page-group.tsx index 195be4700..9f2a7984e 100644 --- a/packages/frontend/core/src/components/page-list/page-group.tsx +++ b/packages/frontend/core/src/components/page-list/page-group.tsx @@ -9,7 +9,7 @@ import { useLiveData, useService } from '@toeverything/infra'; import clsx from 'clsx'; import { selectAtom } from 'jotai/utils'; import type { MouseEventHandler } from 'react'; -import { useCallback, useMemo, useState } from 'react'; +import { memo, useCallback, useMemo, useState } from 'react'; import { CollectionListItem } from './collections/collection-list-item'; import { PageListItem } from './docs/page-list-item'; @@ -240,7 +240,7 @@ export const PageListItemRenderer = (item: ListItem) => { ); }; -export const CollectionListItemRenderer = (item: ListItem) => { +export const CollectionListItemRenderer = memo((item: ListItem) => { const props = useAtomValue(listsPropsAtom); const { selectionActive } = useAtomValue(selectionStateAtom); const collection = item as CollectionMeta; @@ -252,7 +252,9 @@ export const CollectionListItemRenderer = (item: ListItem) => { })} /> ); -}; +}); + +CollectionListItemRenderer.displayName = 'CollectionListItemRenderer'; export const TagListItemRenderer = (item: ListItem) => { const props = useAtomValue(listsPropsAtom); diff --git a/packages/frontend/core/src/components/pure/workspace-mode-filter-tab/index.tsx b/packages/frontend/core/src/components/pure/workspace-mode-filter-tab/index.tsx index c34b9148f..816c09a2e 100644 --- a/packages/frontend/core/src/components/pure/workspace-mode-filter-tab/index.tsx +++ b/packages/frontend/core/src/components/pure/workspace-mode-filter-tab/index.tsx @@ -1,9 +1,9 @@ import { RadioGroup, type RadioItem } from '@affine/component'; import type { AllPageFilterOption } from '@affine/core/components/atoms'; import { allPageFilterSelectAtom } from '@affine/core/components/atoms'; -import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; +import { WorkbenchService } from '@affine/core/modules/workbench'; import { useI18n } from '@affine/i18n'; -import { useService, WorkspaceService } from '@toeverything/infra'; +import { useService } from '@toeverything/infra'; import { useAtom } from 'jotai'; import { useCallback, useEffect, useMemo, useState } from 'react'; @@ -14,26 +14,25 @@ export const WorkspaceModeFilterTab = ({ }: { activeFilter: AllPageFilterOption; }) => { - const workspace = useService(WorkspaceService).workspace; const t = useI18n(); const [value, setValue] = useState(activeFilter); const [filterMode, setFilterMode] = useAtom(allPageFilterSelectAtom); - const { jumpToCollections, jumpToTags, jumpToPage } = useNavigateHelper(); + const workbenchService = useService(WorkbenchService); const handleValueChange = useCallback( (value: AllPageFilterOption) => { switch (value) { case 'collections': - jumpToCollections(workspace.id); + workbenchService.workbench.openCollections(); break; case 'tags': - jumpToTags(workspace.id); + workbenchService.workbench.openTags(); break; case 'docs': - jumpToPage(workspace.id, 'all'); + workbenchService.workbench.openAll(); break; } }, - [jumpToCollections, jumpToPage, jumpToTags, workspace] + [workbenchService.workbench] ); useEffect(() => { diff --git a/packages/frontend/core/src/components/root-app-sidebar/index.tsx b/packages/frontend/core/src/components/root-app-sidebar/index.tsx index 0b3b1c94d..6f003c918 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/index.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/index.tsx @@ -1,4 +1,3 @@ -import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { AddPageButton, AppDownloadButton, @@ -23,7 +22,6 @@ import { } from '@affine/core/modules/explorer'; import { ExplorerTags } from '@affine/core/modules/explorer/views/sections/tags'; import { CMDKQuickSearchService } from '@affine/core/modules/quicksearch/services/cmdk'; -import { isNewTabTrigger } from '@affine/core/utils'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import type { Doc } from '@blocksuite/affine/store'; @@ -35,17 +33,11 @@ import { SettingsIcon, } from '@blocksuite/icons/rc'; import type { Workspace } from '@toeverything/infra'; -import { - useLiveData, - useService, - useServices, - WorkspaceService, -} from '@toeverything/infra'; -import type { MouseEvent, ReactElement } from 'react'; -import { useCallback } from 'react'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; +import type { ReactElement } from 'react'; +import { memo, useCallback } from 'react'; import { WorkbenchService } from '../../modules/workbench'; -import { usePageHelper } from '../blocksuite/block-suite-page-list/utils'; import { WorkspaceNavigator } from '../workspace-selector'; import { quickSearch, @@ -72,42 +64,43 @@ export type RootAppSidebarProps = { }; }; +const AllDocsButton = () => { + const t = useI18n(); + const { workbenchService } = useServices({ + WorkbenchService, + }); + const workbench = workbenchService.workbench; + const allPageActive = useLiveData( + workbench.location$.selector(location => location.pathname === '/all') + ); + + return ( + } active={allPageActive} to={'/all'}> + + {t['com.affine.workspaceSubPath.all']()} + + + ); +}; + /** * This is for the whole affine app sidebar. * This component wraps the app sidebar in `@affine/component` with logic and data. * */ -export const RootAppSidebar = (): ReactElement => { - const { workbenchService, workspaceService, cMDKQuickSearchService } = - useServices({ - WorkspaceService, - WorkbenchService, - CMDKQuickSearchService, - }); - const currentWorkspace = workspaceService.workspace; +export const RootAppSidebar = memo((): ReactElement => { + const { workbenchService, cMDKQuickSearchService } = useServices({ + WorkbenchService, + CMDKQuickSearchService, + }); const t = useI18n(); const globalDialogService = useService(GlobalDialogService); const workspaceDialogService = useService(WorkspaceDialogService); const workbench = workbenchService.workbench; - const currentPath = useLiveData( - workbench.location$.map(location => location.pathname) - ); const onOpenQuickSearchModal = useCallback(() => { cMDKQuickSearchService.toggle(); }, [cMDKQuickSearchService]); - const allPageActive = currentPath === '/all'; - - const pageHelper = usePageHelper(currentWorkspace.docCollection); - - const onClickNewPage = useAsyncCallback( - async (e?: MouseEvent) => { - pageHelper.createPage(undefined, isNewTabTrigger(e) ? 'new-tab' : true); - track.$.navigationPanel.$.createDoc(); - }, - [pageHelper] - ); - const onOpenSettingModal = useCallback(() => { globalDialogService.open('setting', { activeTab: 'appearance', @@ -169,13 +162,9 @@ export const RootAppSidebar = (): ReactElement => { data-event-props="$.navigationPanel.$.quickSearch" onClick={onOpenQuickSearchModal} /> - + - } active={allPageActive} to={'/all'}> - - {t['com.affine.workspaceSubPath.all']()} - - + { ); -}; +}); RootAppSidebar.displayName = 'memo(RootAppSidebar)'; diff --git a/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx index ff2e756ac..725bd23a8 100644 --- a/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx +++ b/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx @@ -1,24 +1,35 @@ import { IconButton } from '@affine/component'; +import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils'; +import { isNewTabTrigger } from '@affine/core/utils'; import { useI18n } from '@affine/i18n'; +import track from '@affine/track'; import { PlusIcon } from '@blocksuite/icons/rc'; +import { useService, WorkspaceService } from '@toeverything/infra'; import clsx from 'clsx'; import type React from 'react'; -import type { MouseEventHandler } from 'react'; +import { type MouseEvent, useCallback } from 'react'; import * as styles from './index.css'; interface AddPageButtonProps { - onClick?: MouseEventHandler; className?: string; style?: React.CSSProperties; } const sideBottom = { side: 'bottom' as const }; -export function AddPageButton({ - onClick, - className, - style, -}: AddPageButtonProps) { +export function AddPageButton({ className, style }: AddPageButtonProps) { + const workspaceService = useService(WorkspaceService); + const currentWorkspace = workspaceService.workspace; + const pageHelper = usePageHelper(currentWorkspace.docCollection); + + const onClickNewPage = useCallback( + (e?: MouseEvent) => { + pageHelper.createPage(undefined, isNewTabTrigger(e) ? 'new-tab' : true); + track.$.navigationPanel.$.createDoc(); + }, + [pageHelper] + ); + const t = useI18n(); return ( @@ -28,8 +39,8 @@ export function AddPageButton({ data-testid="sidebar-new-page-button" style={style} className={clsx([styles.root, className])} - onClick={onClick} - onAuxClick={onClick} + onClick={onClickNewPage} + onAuxClick={onClickNewPage} > diff --git a/packages/frontend/core/src/modules/explorer/views/tree/node.tsx b/packages/frontend/core/src/modules/explorer/views/tree/node.tsx index 6bb4bcba0..c6eb3baa1 100644 --- a/packages/frontend/core/src/modules/explorer/views/tree/node.tsx +++ b/packages/frontend/core/src/modules/explorer/views/tree/node.tsx @@ -89,6 +89,34 @@ interface WebExplorerTreeNodeProps extends BaseExplorerTreeNodeProps { dropEffect?: ExplorerTreeNodeDropEffect; } +/** + * specific rename modal for explorer tree node, + * Separate it into a separate component to prevent re-rendering the entire component when width changes. + */ +const ExplorerTreeNodeRenameModal = ({ + setRenaming, + handleRename, + rawName, +}: { + setRenaming: (renaming: boolean) => void; + handleRename: (newName: string) => void; + rawName: string | undefined; +}) => { + const appSidebarService = useService(AppSidebarService).sidebar; + const sidebarWidth = useLiveData(appSidebarService.width$); + return ( + +
+ + ); +}; + export const ExplorerTreeNode = ({ children, icon: Icon, @@ -126,9 +154,6 @@ export const ExplorerTreeNode = ({ const [lastInGroup, setLastInGroup] = useState(false); const rootRef = useRef(null); - const appSidebarService = useService(AppSidebarService).sidebar; - const sidebarWidth = useLiveData(appSidebarService.width$); - const { emoji, name } = useMemo(() => { if (!extractEmojiAsIcon || !rawName) { return { @@ -379,16 +404,12 @@ export const ExplorerTreeNode = ({
- {renameable && ( - -
- + {renameable && renaming && ( + )}
); diff --git a/packages/frontend/core/src/modules/workbench/index.ts b/packages/frontend/core/src/modules/workbench/index.ts index 23cfbcfe5..0a5341283 100644 --- a/packages/frontend/core/src/modules/workbench/index.ts +++ b/packages/frontend/core/src/modules/workbench/index.ts @@ -1,6 +1,7 @@ export { View as WorkbenchView } from './entities/view'; export { Workbench } from './entities/workbench'; export { ViewScope } from './scopes/view'; +export { ViewService } from './services/view'; export { WorkbenchService } from './services/workbench'; export { useBindWorkbenchToBrowserRouter } from './view/browser-adapter'; export { useIsActiveView } from './view/use-is-active-view';