在Next.js中实现页面级别KeepAlive

在Next.js中实现页面级别KeepAlive

前提:

  1. 基于App Router的路由模式

  2. 使用客户端组件(服务端组件没测试过)

很简单, 只需要三个文件

组件名称 作用
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
}

用法

  1. 先包裹最外层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>
  )
}
  1. 在需要缓存的页面组件中使用KeepAlive包裹
tsx 复制代码
// page.tsx
import { KeepAlive } from '@/components/KeepAlive/KeepAlive'

export default function Page() {
  return (
    <KeepAlive>
      <div>这是一个需要缓存的页面内容</div>
    </KeepAlive>
  )
}
  1. 测试结果, 切换页面时, 该页面状态会被保留, 如果没保留, 请检查是否正确包裹, KeepAlive是否包裹正确

如果还是异常, 请忽略该文章, 就当没看过...

相关推荐
前端不太难1 天前
从 Navigation State 反推架构腐化
前端·架构·react
前端程序猿之路1 天前
Next.js 入门指南 - 从 Vue 角度的理解
前端·vue.js·语言模型·ai编程·入门·next.js·deepseek
大布布将军1 天前
⚡️ 深入数据之海:SQL 基础与 ORM 的应用
前端·数据库·经验分享·sql·程序人生·面试·改行学it
川贝枇杷膏cbppg1 天前
Redis 的 RDB 持久化
前端·redis·bootstrap
JIngJaneIL1 天前
基于java+ vue农产投入线上管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
天外天-亮1 天前
v-if、v-show、display: none、visibility: hidden区别
前端·javascript·html
jump_jump1 天前
手写一个 Askama 模板压缩工具
前端·性能优化·rust
be or not to be1 天前
HTML入门系列:从图片到表单,再到音视频的完整实践
前端·html·音视频
90后的晨仔1 天前
在macOS上无缝整合:为Claude Code配置魔搭社区免费API完全指南
前端
沿着路走到底1 天前
JS事件循环
java·前端·javascript