前言

笔者百无聊赖,把项目中的react-router升级到最新版本来体验体验

 1"react-router": "6.8.1",
 2"react-router-dom": "6.8.1",

令我惊讶的是,最新版本的router官网甚至连翻译都没有,也许是我没找到,官网还是全英的

因为项目中react是18,router又升级到了6.8这就导致了以前用的react-router-cache-route不起作用了

于是笔者网上找了许久都没找到现成的解决方案,所以就只能自己边看文档边摸索了

最后算是解决了keepAlive,所以希望能帮助到遇到相同问题的同学

正文

说真的,router6.8真的变化很大,很多用法有实质性的改变,感觉router也在往拥抱函数式的路上渐行渐远啊。

先来看下以下代码,用于渲染两个页面A & B

 1function A() {
 2    const navigate = useNavigate();
 3    return <div>
 4        <h1 onClick={() => navigate('/b')}>A</h1>
 5        <input type="text" />
 6    </div>
 7}
 8
 9function B() {
10    const navigate = useNavigate();
11    return <div>
12        <h1 onClick={() => navigate('/a')}>B</h1>
13        <input type="text" />
14    </div>
15}
16
17
18const routes: RouteObject[] = [
19    {
20        path: '/a',
21        element: <A />,
22    },
23    {
24        path: '/b',
25        element: <B />,
26    }
27]
28
29const router = createBrowserRouter(routes, {
30    basename: process.env.PUBLIC_URL
31})
32
33
34function App() {
35    
36    return (
37        <Suspense fallback={<div />} >
38            <RouterProvider router={router} />
39        </Suspense>
40    );
41}
42
43export default App;
44

个人认为不同点在于应用路由的方式 不同于之前的Switch route

而是采用了 createBrowserRouter RouterProvider useRoutes

具体的用法细节可以参考官方文档哈

上面的代码在页面上的效果是这样的:

我们可以实现A - B之间的切换 但是A& B页面的输入内容是没法保留的

所以我们需要实现keep alive 切换的同时保留我们在页面留下的痕迹

实现 KeepAlive

实现的关键在于router6.8 提供了useOutLet 让我们可以捕获匹配到的子元素

那么我们干脆自己存储子元素 再自行判断路由来做条件渲染就好了

由于要alive当前页面的元素,势必就只能采用样式隐藏的方式 来做 这也是当前社区主流的做法

所以我们根据上面的思路设计存储组件:

 1import { useUpdate } from "ahooks";
 2import { useEffect, useRef } from "react";
 3import { useLocation, useOutlet } from "react-router-dom";
 4
 5function KeepAlive() {
 6    const componentList = useRef(new Map());
 7    const outLet = useOutlet();
 8    const { pathname } = useLocation();
 9    const forceUpdate = useUpdate();
10
11    useEffect(() => {
12
13        if (!componentList.current.has(pathname)) {
14            componentList.current.set(pathname, outLet);
15        }
16        forceUpdate();
17    }, [pathname]);
18
19    return <div>
20        {
21            Array.from(componentList.current).map(([key, component]) =>
22                <div key={key} style={{display: pathname === key ? 'block': 'none'}}>
23                    {component}
24                </div>
25            )
26        }
27    </div>
28}
29
30export default KeepAlive;

原理很简单,使用map结构 缓存url -> outlet的映射 然后再循环列表渲染

keepalive组件做好了,我们需要调整routes的结构

 1
 2const routes: RouteObject[] = [
 3    {
 4        path: "/",
 5        element: <KeepAlive />,
 6        children: [
 7            {
 8                path: '/a',
 9                element: <A />,
10            },
11            {
12                path: '/b',
13                element: <B />,
14            }
15        ],
16    }
17]

我们在顶层的父级路径使用,后续所有的路由组件都会应用到keep alive功能

现在再来看看效果吧~

所以,keepAlive并不复杂,如果遇到相同的问题,希望对大家能有点帮助吧

个人笔记记录 2021 ~ 2025