在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是否包裹正确

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

相关推荐
小小小小宇7 小时前
Harness Engineering 与 AI 联动
前端
鱼人7 小时前
HTML5 页面性能优化大全
前端
ping某7 小时前
专栏-null 和 undefined 到底是什么?
前端·javascript·后端
用户900463370407 小时前
5MB vs 4KB vs 无限大:浏览器存储谁更强?
前端
小小小小宇7 小时前
Harness Engineering 全解析与应用
前端
牧艺8 小时前
cos-design v3.0:从 15 个 Demo 到 49 个组件的视觉特效库
前端·视觉设计
lichenyang4538 小时前
ASCF 架构升级总览:WebRuntimePage 为什么要变薄
前端
道友可好8 小时前
从今天开始:你的第一个 Harness Engineering 实践
前端·人工智能·后端
Linsk8 小时前
组件 = 模板 + 业务逻辑
java·前端·vue.js
二月龙8 小时前
移动端 H5 页面开发:响应式适配 + 低版本兼容实战指南
前端