Skip to content

Commit a35debb

Browse files
committed
✨ feat(组件新增): 基于原 TabBar 组件重新创建ActivityTabBar组件,基于 React 19.2 Activity 组件实现,增强了标签页的功能与性能。同时更新了相关文档和使用示例,确保与新组件的兼容性。
1 parent 10150e9 commit a35debb

7 files changed

Lines changed: 1476 additions & 98 deletions

File tree

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"@types/crypto-js": "^4.2.2",
5353
"@types/lodash-es": "^4.17.12",
5454
"@types/node": "^22.18.8",
55-
"@types/react": "^19.2.0",
55+
"@types/react": "^19.2.1",
5656
"@types/react-dom": "^19.2.0",
5757
"@types/react-is": "^19.2.0",
5858
"@types/react-resizable": "^3.0.8",
@@ -68,6 +68,7 @@
6868
"vite-plugin-mock-dev-server": "^2.0.1"
6969
},
7070
"engines": {
71-
"node": ">=22.12.0"
71+
"node": ">=22.12.0",
72+
"react": ">=19.2.0"
7273
}
7374
}
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import React, { useRef, useEffect, useState, useCallback } from 'react';
2+
import { Activity } from 'react';
3+
import { useTabStore } from '@/stores/tabStore';
4+
5+
interface ActivityKeepAliveProps {
6+
children: React.ReactNode;
7+
}
8+
9+
interface CacheItem {
10+
component: React.ReactElement;
11+
scrollTop: number;
12+
scrollLeft: number;
13+
}
14+
15+
const ActivityKeepAlive: React.FC<ActivityKeepAliveProps> = ({ children }) => {
16+
const { tabs, activeKey } = useTabStore();
17+
const cacheRef = useRef<Map<string, CacheItem>>(new Map());
18+
const [currentComponent, setCurrentComponent] = useState<React.ReactElement | null>(null);
19+
const containerRef = useRef<HTMLDivElement>(null);
20+
21+
// 保存当前页面的滚动位置
22+
const saveScrollPosition = (key: string) => {
23+
if (containerRef.current) {
24+
const scrollTop = containerRef.current.scrollTop;
25+
const scrollLeft = containerRef.current.scrollLeft;
26+
27+
const cached = cacheRef.current.get(key);
28+
if (cached) {
29+
cached.scrollTop = scrollTop;
30+
cached.scrollLeft = scrollLeft;
31+
}
32+
}
33+
};
34+
35+
// 恢复页面的滚动位置
36+
const restoreScrollPosition = (key: string) => {
37+
const cached = cacheRef.current.get(key);
38+
if (cached && containerRef.current) {
39+
// 使用setTimeout确保DOM更新后再设置滚动位置
40+
setTimeout(() => {
41+
if (containerRef.current) {
42+
containerRef.current.scrollTop = cached.scrollTop;
43+
containerRef.current.scrollLeft = cached.scrollLeft;
44+
}
45+
}, 0);
46+
}
47+
};
48+
49+
// 清除指定key的缓存
50+
const clearCache = useCallback((key: string) => {
51+
if (cacheRef.current.has(key)) {
52+
cacheRef.current.delete(key);
53+
}
54+
}, []);
55+
56+
// 清除所有缓存
57+
const clearAllCache = useCallback(() => {
58+
cacheRef.current.clear();
59+
}, []);
60+
61+
// 智能清理缓存 - 当缓存数量过多时,清理最久未使用的缓存
62+
const smartClearCache = useCallback(() => {
63+
const maxCacheSize = 10; // 最大缓存数量
64+
if (cacheRef.current.size > maxCacheSize) {
65+
const cacheEntries = Array.from(cacheRef.current.entries());
66+
67+
// 只保留配置了keepalive的页面
68+
const keepAliveTabs = tabs.filter((tab) => tab.route?.meta?.keepAlive === true);
69+
const keepAliveKeys = keepAliveTabs.map((tab) => tab.key);
70+
71+
// 过滤出需要保留的缓存(当前活跃页面和最近使用的keepalive页面)
72+
const keysToKeep = [
73+
activeKey,
74+
...cacheEntries
75+
.filter(([key]) => keepAliveKeys.includes(key))
76+
.slice(-5)
77+
.map(([key]) => key),
78+
];
79+
80+
const keysToRemove = cacheEntries
81+
.map(([key]) => key)
82+
.filter((key) => !keysToKeep.includes(key))
83+
.slice(0, cacheRef.current.size - maxCacheSize);
84+
85+
for (const key of keysToRemove) {
86+
clearCache(key);
87+
}
88+
}
89+
}, [activeKey, clearCache, tabs]);
90+
91+
useEffect(() => {
92+
if (!activeKey) return;
93+
94+
// 保存之前页面的滚动位置
95+
const previousActiveKey = Object.keys(cacheRef.current).find((key) => key !== activeKey);
96+
if (previousActiveKey) {
97+
saveScrollPosition(previousActiveKey);
98+
}
99+
100+
// 获取当前激活的tab信息
101+
const currentTab = tabs.find((tab) => tab.key === activeKey);
102+
const shouldCache = currentTab?.route?.meta?.keepAlive === true;
103+
104+
if (shouldCache) {
105+
// 如果配置了keepalive,检查缓存中是否已有当前页面
106+
let cached = cacheRef.current.get(activeKey);
107+
108+
if (!cached) {
109+
// 如果没有缓存,创建新的缓存项
110+
cached = {
111+
component: children as React.ReactElement,
112+
scrollTop: 0,
113+
scrollLeft: 0,
114+
};
115+
cacheRef.current.set(activeKey, cached);
116+
}
117+
118+
// 设置当前显示的组件
119+
setCurrentComponent(cached.component);
120+
121+
// 恢复滚动位置
122+
restoreScrollPosition(activeKey);
123+
} else {
124+
// 如果没有配置keepalive,直接渲染组件,不进行缓存
125+
setCurrentComponent(children as React.ReactElement);
126+
127+
// 如果之前有缓存,清除它
128+
if (cacheRef.current.has(activeKey)) {
129+
clearCache(activeKey);
130+
}
131+
}
132+
133+
// 智能清理缓存
134+
smartClearCache();
135+
}, [activeKey, children, tabs, clearCache, smartClearCache]);
136+
137+
// 监听系统刷新事件
138+
useEffect(() => {
139+
const handleBeforeUnload = () => {
140+
clearAllCache();
141+
};
142+
143+
// 监听页面刷新/关闭
144+
window.addEventListener('beforeunload', handleBeforeUnload);
145+
146+
return () => {
147+
window.removeEventListener('beforeunload', handleBeforeUnload);
148+
};
149+
}, [clearAllCache]);
150+
151+
// 监听用户退出登录事件
152+
useEffect(() => {
153+
const handleStorageChange = (e: StorageEvent) => {
154+
// 监听 localStorage 变化,检测用户登录状态变化
155+
if (e.key === 'user-storage') {
156+
try {
157+
const userData = JSON.parse(e.newValue || '{}');
158+
if (!userData.isLogin) {
159+
// 用户退出登录,清空所有缓存
160+
clearAllCache();
161+
}
162+
} catch (error) {
163+
// 解析失败,忽略
164+
}
165+
}
166+
};
167+
168+
window.addEventListener('storage', handleStorageChange);
169+
170+
return () => {
171+
window.removeEventListener('storage', handleStorageChange);
172+
};
173+
}, [clearAllCache]);
174+
175+
// 清理不存在的tab对应的缓存
176+
useEffect(() => {
177+
const tabKeys = tabs.map((tab) => tab.key);
178+
const cacheKeys = Array.from(cacheRef.current.keys());
179+
180+
cacheKeys.forEach((key) => {
181+
if (!tabKeys.includes(key)) {
182+
// 当tab被关闭时,清除对应的缓存
183+
clearCache(key);
184+
}
185+
});
186+
}, [tabs, clearCache]);
187+
188+
// 暴露清除缓存的方法给外部使用
189+
useEffect(() => {
190+
// 将清除缓存的方法挂载到 window 对象上,方便调试和外部调用
191+
(window as any).__keepAliveClearCache = clearCache;
192+
(window as any).__keepAliveClearAllCache = clearAllCache;
193+
(window as any).__keepAliveSmartClearCache = smartClearCache;
194+
195+
return () => {
196+
delete (window as any).__keepAliveClearCache;
197+
delete (window as any).__keepAliveClearAllCache;
198+
delete (window as any).__keepAliveSmartClearCache;
199+
};
200+
}, [clearCache, clearAllCache, smartClearCache]);
201+
202+
if (!currentComponent) {
203+
return null;
204+
}
205+
206+
// 使用React 19.2的Activity组件来实现keepalive功能
207+
return (
208+
<div ref={containerRef} className="h-full relative flex flex-col p-4">
209+
<Activity mode={activeKey ? 'visible' : 'hidden'}>
210+
{currentComponent}
211+
</Activity>
212+
</div>
213+
);
214+
};
215+
216+
export default ActivityKeepAlive;

0 commit comments

Comments
 (0)