- keepalive 页面 在某些特定的场景下还是特别有用。
- 最终效果就是这样,页面的state 被缓存。
思路分析
1、传入需要缓存的页面 。
2、使用react 的context 来缓存当前页面的所有节点
3、二次封装 useOutlet ,根据缓存的 页面 来实现自定义渲染。
传入需要缓存的页面
// App.tsx
import { Link, RouterProvider, createBrowserRouter } from "react-router-dom"
import KeepAlive from "./components/Keepalive"
import useKeepOutlet from "./components/useKeepOutlet.tsx"
import { useState } from "react"
// home 页面
const Home = () => {
const [count, setCount] = useState(0)
return (
<div>
<div>{count}</div>
<button onClick={() => setCount((c) => c += 1)}>+ 1</button>
<Link to={'/list'}>list</Link>
11111
</div>
)
}
// list 页面
const List = () => {
const [count, setCount] = useState(0)
return <div>
<div>{count}</div>
list
<button onClick={() => setCount((c) => c += 1)}>+ 1</button>
<Link to={'/home'}>home</Link>
</div>
}
// 所有页面的父级组件,在router 中配置
const Index = () => {
// 二次封装的useOutlet
const element = useKeepOutlet(routeConfig)
return <div>{ element }11</div>
}
// router 配置
const routeConfig = [
{
path: "/",
element: <Index></Index>,
children: [
{
path:'/home',
element:<Home />
},
{
path:'/list',
element:<List />
},
]
}
]
export const router = createBrowserRouter(routeConfig);
const App = () => {
return (
<KeepAlive keepPath={['list']}>
<RouterProvider router={router}/>
</KeepAlive>
)
}
export default App
KeepAlive 组件里面包含 context
import { useContext, PropsWithChildren } from "react";
import keepaliveContext from "./core"
interface PropsType extends PropsWithChildren {
// 缓存页面的路由
keepPath: string[]
}
const KeepAlive = (props) => {
return <keepaliveContext.Provider value={{ keepElement: [], keepPath: props.keepPath }} >{props.children}</keepaliveContext.Provider>
}
export default KeepAlive
创建 keepalive 的 context
// ./core.tsx
interface KeepaliveContextType {
// 需要缓存的页面路径
keepPath: string[]
// 当缓存页面加载过,那么被加载的页面会存在这里面
keepElement: Record<string, React.ReactNode>
}
const KeepaliveContext = createContext<KeepaliveContextType>({
keepPath: [],
keepElement: {}
})
export default KeepaliveContext
二次封装useOutlet
- useOutlet 是 react-router-dom 的新的hook,返回的是路由的节点元素。
// useKeepOutlet.tsx
const useKeepOutlet = () => {
// 获取到路由节点
const element = useOutlet()
// 拿出 context 的数据
const { keepElement, keepPath } = useContext(KeepaliveContext)
// 当前路由的path
const currentPathname = useLocation().pathname
// 判断当前页面是要缓存
const keep = keepPath.find(item => currentPathname.includes(item))
if(keep) {
keepElement[currentPathname] = element
}
return (
<div>
{
Object.entries(keepElement).map(([pathName, element]) => (
<div
key={pathName}
hidden={!matchPath(location.pathname, pathName)}
>
{element}
</div>
))
}
{!keep && element}
</div>
)
}
export default useKeepOutlet
}