Skip to content

Commit a10965d

Browse files
committed
pref: 优化多个标签页导致性能下降的问题
1 parent 277f12e commit a10965d

5 files changed

Lines changed: 100 additions & 52 deletions

File tree

src/app/core/main/editor/editor-layout.tsx

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ import { EmptyState } from './empty-state'
1010
import { FolderView } from './folder'
1111
import { UnsupportedFile } from './unsupported-file'
1212

13+
// 常量:扩展名到类型的映射(避免每次渲染时重新创建)
14+
const MARKDOWN_EXTENSIONS = new Set([
15+
'md', 'txt', 'markdown', 'py', 'js', 'ts', 'jsx', 'tsx', 'css', 'scss', 'less',
16+
'html', 'xml', 'json', 'yaml', 'yml', 'sh', 'bash', 'java', 'c', 'cpp', 'h', 'go',
17+
'rs', 'sql', 'rb', 'php', 'vue', 'svelte', 'astro', 'toml', 'ini', 'conf', 'cfg',
18+
'gitignore', 'env', 'example', 'template'
19+
])
20+
21+
const IMAGE_EXTENSIONS = new Set(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'])
22+
1323
export function EditorLayout() {
1424
const {
1525
activeFilePath,
@@ -68,10 +78,10 @@ export function EditorLayout() {
6878
const extension = path.split('.').pop()?.toLowerCase()
6979
if (!extension) return 'unknown'
7080

71-
if (['md', 'txt', 'markdown', 'py', 'js', 'ts', 'jsx', 'tsx', 'css', 'scss', 'less', 'html', 'xml', 'json', 'yaml', 'yml', 'sh', 'bash', 'java', 'c', 'cpp', 'h', 'go', 'rs', 'sql', 'rb', 'php', 'vue', 'svelte', 'astro', 'toml', 'ini', 'conf', 'cfg', 'gitignore', 'env', 'example', 'template'].includes(extension)) {
81+
if (MARKDOWN_EXTENSIONS.has(extension)) {
7282
return 'markdown'
7383
}
74-
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(extension)) {
84+
if (IMAGE_EXTENSIONS.has(extension)) {
7585
return 'image'
7686
}
7787
return 'unknown'
@@ -237,20 +247,28 @@ export function EditorLayout() {
237247
emitter.emit('editor-file-close', { path: closedPath })
238248
delete tabContentsRef.current[closedPath]
239249

240-
// Bug fix: Get closedTab from the current ref value (updated synchronously in useEffect)
250+
// Get closedTab from the current ref value
241251
const closedTab = tabsRef.current.find(t => t.path === closedPath)
242-
if (closedTab) {
243-
removeTab(closedTab.id)
244-
}
245-
246-
// Bug fix: Only switch active tab if we're closing the currently active tab
247-
// Use localActiveTabId which is kept in sync via useEffect
248-
if (closedTab && localActiveTabId === closedTab.id) {
249-
if (tabsRef.current.length > 1) {
250-
const currentIndex = tabsRef.current.findIndex(t => t.id === closedTab.id)
251-
const targetTab = tabsRef.current[Math.max(0, currentIndex - 1)] || tabsRef.current[tabsRef.current.length - 1]
252-
setActiveTabId(targetTab.id)
253-
setActiveFilePath(targetTab.path)
252+
if (!closedTab) return
253+
254+
// Save the current tabs count before removing
255+
const tabsCountBeforeRemove = tabsRef.current.length
256+
257+
// Remove the tab
258+
removeTab(closedTab.id)
259+
260+
// Only switch active tab if we're closing the currently active tab
261+
if (localActiveTabId === closedTab.id) {
262+
if (tabsCountBeforeRemove > 1) {
263+
// Find the new target tab from the updated tabsRef after removal
264+
const remainingTabs = tabsRef.current.filter(t => t.id !== closedTab.id)
265+
if (remainingTabs.length > 0) {
266+
// Try to select the tab to the left, otherwise select the last one
267+
const currentIndex = tabsRef.current.findIndex(t => t.id === closedTab.id)
268+
const targetTab = remainingTabs[Math.max(0, currentIndex - 1)] || remainingTabs[remainingTabs.length - 1]
269+
setActiveTabId(targetTab.id)
270+
setActiveFilePath(targetTab.path)
271+
}
254272
} else {
255273
setActiveTabId('')
256274
setActiveFilePath('')
@@ -381,8 +399,8 @@ export function EditorLayout() {
381399
onCloseRightTabs={handleCloseRightTabs}
382400
/>
383401

384-
{/* Content panels - all rendered, only active one visible */}
385-
{tabs.map(tab => renderContentPanel(tab, tab.id === localActiveTabId))}
402+
{/* Only render active tab content - improves performance with many tabs */}
403+
{tabs.filter(tab => tab.id === localActiveTabId).map(tab => renderContentPanel(tab, true))}
386404
</div>
387405
)
388406
}

src/app/core/main/editor/tab-bar.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useCallback, useRef, useState, useEffect } from 'react'
3+
import { useCallback, useRef, useState, useEffect, memo } from 'react'
44
import { X, FileText, Folder, Plus } from 'lucide-react'
55
import { useTranslations } from 'next-intl'
66
import { cn } from '@/lib/utils'
@@ -197,6 +197,9 @@ function SortableTabWithMenu({
197197
)
198198
}
199199

200+
// Memoize to prevent unnecessary re-renders
201+
const MemoizedSortableTabWithMenu = memo(SortableTabWithMenu)
202+
200203
export function TabBar({
201204
tabs,
202205
activeTabId,
@@ -356,7 +359,7 @@ export function TabBar({
356359
strategy={horizontalListSortingStrategy}
357360
>
358361
{tabs.map((tab) => (
359-
<SortableTabWithMenu
362+
<MemoizedSortableTabWithMenu
360363
key={tab.id}
361364
tab={tab}
362365
isActive={activeTabId === tab.id}

src/app/core/main/file/file-item.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ export function FileItem({ item, focusSidebar }: { item: DirTree; focusSidebar?:
8989

9090
const isRoot = path.split('/').length === 1
9191
const folderPath = path.includes('/') ? path.split('/').slice(0, -1).join('/') : ''
92-
const cacheTree = cloneDeep(fileTree)
93-
const currentFolder = getCurrentFolder(folderPath, cacheTree)
92+
// 不需要 cloneDeep,因为 getCurrentFolder 只读取数据不修改
93+
const currentFolder = getCurrentFolder(folderPath, fileTree)
9494

9595
// 优化的输入处理,支持输入法
9696
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
@@ -209,6 +209,8 @@ export function FileItem({ item, focusSidebar }: { item: DirTree; focusSidebar?:
209209
}
210210
}
211211
} else {
212+
// 根目录文件:需要克隆 fileTree 来更新
213+
const cacheTree = cloneDeep(fileTree)
212214
const index = cacheTree.findIndex(file => file.name === item.name)
213215
if (index !== undefined && index !== -1) {
214216
const current = cacheTree[index]
@@ -220,8 +222,8 @@ export function FileItem({ item, focusSidebar }: { item: DirTree; focusSidebar?:
220222
cacheTree.splice(index, 1)
221223
}
222224
}
225+
setFileTree(cacheTree)
223226
}
224-
setFileTree(cacheTree)
225227

226228
// 删除向量数据库中的记录
227229
try {
@@ -380,11 +382,14 @@ export function FileItem({ item, focusSidebar }: { item: DirTree; focusSidebar?:
380382
currentFolder.children[fileIndex].isEditing = false
381383
}
382384
} else {
385+
// 根目录文件:需要克隆 fileTree 来更新
386+
const cacheTree = cloneDeep(fileTree)
383387
const fileIndex = cacheTree.findIndex(file => file.name === item.name)
384388
if (fileIndex !== -1 && fileIndex !== undefined) {
385389
cacheTree[fileIndex].name = displayName
386390
cacheTree[fileIndex].isEditing = false
387391
}
392+
setFileTree(cacheTree)
388393
}
389394

390395
// 确定是重命名现有文件还是创建新文件
@@ -456,11 +461,15 @@ export function FileItem({ item, focusSidebar }: { item: DirTree; focusSidebar?:
456461
if (index !== undefined && index !== -1 && currentFolder?.children) {
457462
currentFolder?.children?.splice(index, 1)
458463
}
464+
setFileTree(fileTree)
459465
} else {
466+
// 根目录文件:需要克隆 fileTree 来更新
467+
const cacheTree = cloneDeep(fileTree)
460468
const index = cacheTree.findIndex(item => item.name === '')
461469
if (index !== -1) {
462470
cacheTree.splice(index, 1)
463471
}
472+
setFileTree(cacheTree)
464473
}
465474
} else {
466475
// 对于重命名现有文件,如果没有输入新名称,则保持原状态
@@ -469,16 +478,19 @@ export function FileItem({ item, focusSidebar }: { item: DirTree; focusSidebar?:
469478
if (fileIndex !== undefined && fileIndex !== -1) {
470479
currentFolder.children[fileIndex].isEditing = false
471480
}
481+
setFileTree(fileTree)
472482
} else {
483+
// 根目录文件:需要克隆 fileTree 来更新
484+
const cacheTree = cloneDeep(fileTree)
473485
const fileIndex = cacheTree.findIndex(file => file.name === item.name)
474486
if (fileIndex !== -1 && fileIndex !== undefined) {
475487
cacheTree[fileIndex].isEditing = false
476488
}
489+
setFileTree(cacheTree)
477490
}
478491
}
479492
}
480-
481-
setFileTree(cacheTree)
493+
482494
setIsEditing(false)
483495
}
484496

@@ -671,13 +683,16 @@ export function FileItem({ item, focusSidebar }: { item: DirTree; focusSidebar?:
671683
if (index !== undefined && index !== -1 && currentFolder?.children) {
672684
currentFolder?.children?.splice(index, 1)
673685
}
686+
setFileTree(fileTree)
674687
} else {
688+
// 根目录文件:需要克隆 fileTree 来更新
689+
const cacheTree = cloneDeep(fileTree)
675690
const index = cacheTree.findIndex(item => item.name === '')
676691
if (index !== -1) {
677692
cacheTree.splice(index, 1)
678693
}
694+
setFileTree(cacheTree)
679695
}
680-
setFileTree(cacheTree)
681696
setIsEditing(false)
682697
}
683698

src/app/core/main/file/file-manager.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use client'
2-
import React, { useEffect, useState } from "react"
2+
import React, { useEffect, useState, useMemo } from "react"
33
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible"
44
import useArticleStore, { DirTree } from "@/stores/article"
55
import { BaseDirectory, rename, writeTextFile, writeFile } from "@tauri-apps/plugin-fs"
@@ -141,8 +141,11 @@ export function FileManager({ focusSidebar }: { focusSidebar: () => void }) {
141141
}
142142
}, [loadFileTree])
143143

144-
// 根据开关状态过滤文件树
145-
const filteredFileTree = filterFileTree(fileTree, showCloudFiles)
144+
// 根据开关状态过滤文件树 - 使用 useMemo 缓存结果
145+
const filteredFileTree = useMemo(
146+
() => filterFileTree(fileTree, showCloudFiles),
147+
[fileTree, showCloudFiles]
148+
)
146149

147150
return (
148151
<div className={`flex-1 overflow-y-auto ${isDragging && 'outline-2 outline-black outline-dotted -outline-offset-4'}`}>

0 commit comments

Comments
 (0)