在Next.js中实现页面级别KeepAlive
前提:
-
基于App Router的路由模式
-
使用客户端组件(服务端组件没测试过)
很简单, 只需要三个文件
| 组件名称 | 作用 |
|---|---|
| KeepAliveProvider.tsx | 用于包裹最外层layout, 处理缓存组件和非缓存组件的渲染 |
| KeepAliveContext.tsx | 无需解释 |
| KeepAlive.tsx | 用于包裹App Router的页面组件page.tsx |
KeepAliveProvider.tsx
tsx
'use client'
import { KeepAliveContext } from '@/components/KeepAlive/KeepAliveContext'
import { usePathname } from 'next/navigation'
import React, { useState } from 'react'
interface KeepAliveProviderProps {
children: React.ReactNode
}
// 最大缓存数量
const MAX_CACHE_SIZE = 5
export const KeepAliveProvider: React.FC<KeepAliveProviderProps> = ({ children }) => {
const pathname = usePathname()
const [componentList, setComponentList] = useState<Map<string, React.ReactNode>>(new Map())
// 更新缓存组件列表的方法
const updateComponentList = (path: string, component: React.ReactNode) => {
setComponentList((prev) => {
const newMap = new Map(prev)
newMap.set(path, component)
// 如果超过最大缓存数量,再删除最旧的
if (newMap.size > MAX_CACHE_SIZE) {
const oldestKey = newMap.keys().next().value
// 如果存在最旧的key,则删除
if (oldestKey !== undefined) {
newMap.delete(oldestKey)
}
}
return newMap
})
}
return (
<KeepAliveContext.Provider
value={{
componentList,
updateComponentList,
activePath: pathname,
}}
>
{[...componentList.entries()].map(([key, node]) => (
<div
key={key}
style={{
display: key === pathname ? 'block' : 'none',
height: '100%',
}}
>
{node}
</div>
))}
<div className="h-full">{!componentList.get(pathname) && children}</div>
</KeepAliveContext.Provider>
)
}
KeepAliveContext.tsx
tsx
// 创建context
import React, { createContext } from 'react'
interface IKeepAliveContext {
// 缓存的组件列表
componentList: Map<string, React.ReactNode>
// 更新缓存组件列表的方法
updateComponentList: (path: string, component: React.ReactNode) => void
// 当前激活的路径
activePath?: string
}
export const KeepAliveContext = createContext<IKeepAliveContext>({
componentList: new Map<string, React.ReactNode>(),
updateComponentList: () => {},
})
KeepAlive.tsx
tsx
'use client'
import { usePathname } from 'next/navigation'
import React, { useContext, useEffect } from 'react'
import { KeepAliveContext } from './KeepAliveContext'
export const KeepAlive = ({ children }: { children: React.ReactNode }) => {
const { updateComponentList } = useContext(KeepAliveContext)
const pathname = usePathname()
// 每当 children 变化时,更新缓存的组件列表
useEffect(() => {
updateComponentList(pathname, children)
}, [children])
// 该组件本身不渲染任何内容
return null
}
用法
- 先包裹最外层layout (项目根目录的layout.tsx, 并非组件的layout.tsx)
tsx
// layout.tsx
import { KeepAliveProvider } from '@/components/KeepAlive/KeepAliveProvider'
export default function RootLayout({ children }) {
return (
<html>
<body>
<KeepAliveProvider>{children}</KeepAliveProvider>
</body>
</html>
)
}
- 在需要缓存的页面组件中使用KeepAlive包裹
tsx
// page.tsx
import { KeepAlive } from '@/components/KeepAlive/KeepAlive'
export default function Page() {
return (
<KeepAlive>
<div>这是一个需要缓存的页面内容</div>
</KeepAlive>
)
}
- 测试结果, 切换页面时, 该页面状态会被保留, 如果没保留, 请检查是否正确包裹, KeepAlive是否包裹正确
如果还是异常, 请忽略该文章, 就当没看过...