我将实现一个 React 版本的 Keep-Alive 组件,类似于 Vue 中的 keep-alive 功能,并支持 Tab 管理、缓存、关闭等功能。
实现方案
我们将创建以下几个核心组件和功能:
KeepAlive
- 缓存组件容器TabManager
- 管理 Tab 的打开、关闭等操作useTab
- 自定义 Hook 用于组件内访问 Tab 相关状态和方法Activated
和Deactivated
生命周期支持
1. KeepAlive 组件实现
ini
import React, { useState, useEffect, useRef } from 'react';
// 缓存组件实例的容器
const KeepAlive = ({ children, isActive, onActivated, onDeactivated }) => {
const mountedRef = useRef(false);
const childRef = useRef();
// 获取子组件的引用
useEffect(() => {
if (React.Children.count(children) === 1) {
childRef.current = React.Children.only(children).ref;
}
}, [children]);
// 当 isActive 变化时触发相应的生命周期
useEffect(() => {
if (isActive) {
if (!mountedRef.current) {
// 首次挂载
mountedRef.current = true;
} else {
// 从非激活状态变为激活状态
if (typeof onActivated === 'function') {
onActivated();
}
}
} else {
// 从激活状态变为非激活状态
if (typeof onDeactivated === 'function') {
onDeactivated();
}
}
}, [isActive, onActivated, onDeactivated]);
return React.cloneElement(React.Children.only(children), {
ref: (node) => {
if (childRef.current) {
if (typeof childRef.current === 'function') {
childRef.current(node);
} else {
childRef.current.current = node;
}
}
},
});
};
export default KeepAlive;
2. TabManager 和 useTab 实现
javascript
import React, { createContext, useContext, useState, useEffect } from 'react';
// 创建 Tab 上下文
const TabContext = createContext();
// TabManager 组件
export const TabManager = ({ children }) => {
const [tabs, setTabs] = useState([]);
const [activeTabId, setActiveTabId] = useState(null);
// 添加新 Tab
const addTab = (tab) => {
setTabs((prevTabs) => {
// 检查是否已存在相同路径的 Tab
const existingTab = prevTabs.find((t) => t.path === tab.path);
if (existingTab) {
// 如果存在,激活它并返回不变
setActiveTabId(existingTab.id);
return prevTabs;
}
// 不存在则添加新 Tab
const newTab = {
id: Date.now().toString(),
...tab,
};
setActiveTabId(newTab.id);
return [...prevTabs, newTab];
});
};
// 关闭 Tab
const closeTab = (tabId) => {
setTabs((prevTabs) => {
const newTabs = prevTabs.filter((tab) => tab.id !== tabId);
// 如果关闭的是当前激活的 Tab
if (tabId === activeTabId) {
// 激活最后一个 Tab 或者第一个 Tab
if (newTabs.length > 0) {
setActiveTabId(newTabs[newTabs.length - 1].id);
} else {
setActiveTabId(null);
}
}
return newTabs;
});
};
// 关闭其他所有 Tab,保留当前 Tab
const closeOtherTabs = () => {
if (activeTabId) {
setTabs((prevTabs) => prevTabs.filter((tab) => tab.id === activeTabId));
}
};
// 关闭所有 Tab
const closeAllTabs = () => {
setTabs([]);
setActiveTabId(null);
};
// 提供给子组件的值
const value = {
tabs,
activeTabId,
addTab,
closeTab,
closeOtherTabs,
closeAllTabs,
};
return (
<TabContext.Provider value={value}>
{children}
</TabContext.Provider>
);
};
// useTab Hook
export const useTab = () => {
const context = useContext(TabContext);
if (!context) {
throw new Error('useTab must be used within a TabManager');
}
return context;
};
3. Tab 组件实现
javascript
import React from 'react';
import { useTab } from './TabManager';
const Tab = ({ tab }) => {
const { closeTab } = useTab();
return (
<div className="tab">
<span>{tab.title}</span>
<button onClick={() => closeTab(tab.id)}>×</button>
</div>
);
};
export default Tab;
4. TabBar 组件实现
javascript
import React from 'react';
import { useTab } from './TabManager';
import Tab from './Tab';
const TabBar = () => {
const { tabs, activeTabId, closeTab, closeOtherTabs, closeAllTabs } = useTab();
return (
<div className="tab-bar">
<div className="tab-actions">
<button onClick={closeOtherTabs}>关闭其他</button>
<button onClick={closeAllTabs}>关闭所有</button>
</div>
<div className="tabs">
{tabs.map((tab) => (
<Tab
key={tab.id}
tab={tab}
isActive={tab.id === activeTabId}
/>
))}
</div>
</div>
);
};
export default TabBar;
5. RouteTab 组件实现 (核心功能)
javascript
import React, { useEffect } from 'react';
import { useLocation, useHistory } from 'react-router-dom';
import { useTab } from './TabManager';
import KeepAlive from './KeepAlive';
const RouteTab = ({ children }) => {
const location = useLocation();
const history = useHistory();
const { tabs, activeTabId, addTab } = useTab();
// 从路由信息中获取标题
const getTabTitle = () => {
// 这里可以根据路由配置获取标题
// 简单实现:从 location.state 或 pathname 中提取
return location.state?.title || location.pathname.split('/').pop() || '未命名';
};
// 当前路由对应的 Tab
const currentTab = tabs.find((tab) => tab.path === location.pathname);
// 添加新 Tab 或激活现有 Tab
useEffect(() => {
if (location.pathname) {
addTab({
path: location.pathname,
title: getTabTitle(),
});
}
}, [location.pathname, addTab]);
// 如果没有匹配的 Tab,不渲染内容
if (!currentTab) {
return null;
}
// 判断当前 Tab 是否激活
const isActive = currentTab.id === activeTabId;
// 激活和停用回调
const handleActivated = () => {
console.log('Component activated:', currentTab.title);
// 这里可以执行组件激活时的逻辑
};
const handleDeactivated = () => {
console.log('Component deactivated:', currentTab.title);
// 这里可以执行组件停用时的逻辑
};
return (
<KeepAlive isActive={isActive} onActivated={handleActivated} onDeactivated={handleDeactivated}>
{React.cloneElement(children, {
// 可以传递一些额外的 props 给子组件
})}
</KeepAlive>
);
};
export default RouteTab;
6. 完整使用示例
javascript
import React from 'react';
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
import { TabManager } from './TabManager';
import TabBar from './TabBar';
import RouteTab from './RouteTab';
// 示例页面组件
const Page1 = () => {
console.log('Page1 rendered');
return <div>Page 1 Content</div>;
};
const Page2 = () => {
console.log('Page2 rendered');
return <div>Page 2 Content</div>;
};
const Page3 = () => {
console.log('Page3 rendered');
return <div>Page 3 Content</div>;
};
const App = () => {
return (
<Router>
<TabManager>
<div className="app">
<div className="nav">
<Link to="/page1" state={{ title: '页面1' }}>页面1</Link>
<Link to="/page2" state={{ title: '页面2' }}>页面2</Link>
<Link to="/page3" state={{ title: '页面3' }}>页面3</Link>
</div>
<TabBar />
<div className="content">
<Switch>
<Route path="/page1">
<RouteTab>
<Page1 />
</RouteTab>
</Route>
<Route path="/page2">
<RouteTab>
<Page2 />
</RouteTab>
</Route>
<Route path="/page3">
<RouteTab>
<Page3 />
</RouteTab>
</Route>
</Switch>
</div>
</div>
</TabManager>
</Router>
);
};
export default App;