react-router-dom v6版本实现Tabs路由缓存切换功能
概要
摆了半年摊,好久没写代码了,今天有人问我怎么实现React-Router-dom类似标签页缓存。后面看了一下router的官网。很久以前用的是react-router v5那个比较容易实现。v6变化挺大,但了解react的机制和react-router的机制就容易了.
想做到切换标签保留页面的内容不变,就要了解react的机制
首先虚拟DOM的机制就是对比,如果找不到就会重新挂载,找到了就更新。
React-Router的机制就是匹配路径,找到了就返回对应的路由组件,找不到返回为null
思路就是v6版本提供了Outlet这个输出子路元素的组件
一般我们会这样写:
但如果要实现标签的话,就不能这样写。但是切换路由,地址一变它就替换了原来的了。所以要做的就是保留所有打开的标签的子路由元素:
主要目的就是保留所有的元素,隐藏路由就行,这样react diff时,还是会找到对应key的路由,这样它只是会更新路由的组件,而不会重新挂载。
如这样写:
效果
完整代码
新建一个html复制进去就可以运行了
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>React-router-dom tabs</title> <script src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.production.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.production.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7.23.2/babel.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/antd@5.10.1/dist/reset.min.css" rel="external nofollow" > <script src="https://cdn.jsdelivr.net/npm/dayjs@1.11.10/dayjs.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/antd@5.10.1/dist/antd.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@ant-design/pro-components@2.6.30/dist/pro-components.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@remix-run/router@1.10.0/dist/router.umd.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/react-router@6.17.0/dist/umd/react-router.production.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/react-router-dom@6.17.0/dist/umd/react-router-dom.production.min.js"></script> </head> <body> <div id="app"></div> <script type="text/babel" data-preset="env,react"> const { useCallback, useMemo, useEffect, useRef, useState } = React const { ProLayout } = ProComponents const { createHashRouter, useOutlet, Navigate, RouterProvider, useLocation, Link, useNavigate } = ReactRouterDOM const { Tabs, Button, Space, Row, Col, Input, } = antd const Home = () => { return <div>Home <Input></Input></div> } const DemoA = () => { return <div>DemoA <Input></Input></div> } const DemoB = () => { return <div>DemoB <Input></Input></div> } const ViewPage = (props) => { const outlet = useOutlet() return props.render(outlet) } const BasicLayout = () => { const nav = useNavigate() const location = useLocation() const route = routes[0] const [menuDataMap] = useState(() => new Map()) const cacheOutletElements = useRef({}).current const [tabActiveKey, setTabActiveKey] = useState('') const [tabItems, setTabItems] = React.useState([]) const addTab = useCallback((item) => { const existItem = tabItems.find(it => it.key === item.key) if (!existItem) { setTabItems([...tabItems, { key: item.key, path: item.path, label: item.name }]) } setTabActiveKey(item.key) }, [tabItems]) const handleSelectMenu = useCallback((selectedKeys) => { console.log('handleSelectMenu', selectedKeys) let menuKey = selectedKeys[selectedKeys.length - 1] let item = menuDataMap.get(menuKey) if (item && item.path) { addTab(item) } }, [addTab]) const handleTabChange = useCallback((activeKey) => { setTabActiveKey(activeKey) const item = tabItems.find(d => d.key === activeKey) nav(item.path) }, [tabItems, nav]) const handleTabEditChange = useCallback((activeKey, action) => { if (action === 'remove') { delete cacheOutletElements[activeKey] const newItems = tabItems.filter(d => d.key !== activeKey) setTabItems(newItems) if (newItems.length) { handleTabChange(newItems[0].key) } } }, [tabItems, handleTabChange]) const renderView = useCallback((routeElement) => { if (!cacheOutletElements[tabActiveKey]) { cacheOutletElements[tabActiveKey] = <div>{routeElement}</div> } return Object.keys(cacheOutletElements).map(key => { const element = cacheOutletElements[key] if (key === tabActiveKey) { return React.cloneElement(element, { key: key, style: { display: 'block' } }) } else { return React.cloneElement(element, { key: key, style: { display: 'none' } }) } }) }, [cacheOutletElements, tabActiveKey]) return <ProLayout route={route} onSelect={handleSelectMenu} location={location} menuItemRender={(item, defaultDom, menuProps) => { if (item.children) { return defaultDom } menuDataMap.set(item.path, item) return <Link to={item.path}>{defaultDom}</Link> }}> <Tabs hideAdd type='editable-card' onEdit={handleTabEditChange} activeKey={tabActiveKey} onChange={handleTabChange} items={tabItems}> </Tabs> <ViewPage render={renderView}></ViewPage> </ProLayout> } const routes = [{ path: '/', element: <BasicLayout></BasicLayout>, children: [ { index: true, element: <Navigate to="/home"></Navigate> }, { path: 'home', name: "Home", element: <Home></Home> } , { path: 'a', name: "DemoA", element: <DemoA></DemoA> } , { path: 'b', name: "DemoB", element: <DemoB></DemoB> } ] }] const router = createHashRouter(routes, { }) const App = () => { return <RouterProvider router={router}></RouterProvider> } ReactDOM.createRoot(document.getElementById('app')).render(<App></App>) </script> </body> </html>
到此这篇关于react-router-dom v6版本实现Tabs路由缓存切换的文章就介绍到这了,更多相关react-router-dom路由缓存切换内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
详解React Native 采用Fetch方式发送跨域POST请求
这篇文章主要介绍了详解React Native 采用Fetch方式发送跨域POST请求,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧2017-11-11React-View-UI组件库封装Loading加载中源码
这篇文章主要介绍了React-View-UI组件库封装Loading加载样式,主要包括组件介绍,组件源码及组件测试源码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2022-06-06
最新评论