Skip to content

Not based on React.Activity, but implementation is same like it based on Suspense.

Notifications You must be signed in to change notification settings

givingwu/react-keepalive

Repository files navigation

React KeepAlive

一个强大的 React 路由缓存组件库,专为 React Router v6+ 设计,提供灵活的页面状态保持和生命周期管理。了解更多见:

🚀 特性

  • 🔥 智能生命周期useActiveEffect 提供类似 useEffect 的 API,支持激活/失活回调
  • 📦 轻量级设计:核心代码精简,性能优异,零依赖冲突
  • 🔧 完整类型支持:100% TypeScript 编写,提供完整的类型定义
  • 🎯 开箱即用KeepAliveOutlet 组件,零配置集成路由缓存
  • 🔄 灵活控制:支持缓存刷新、销毁、状态查询等完整缓存管理
  • 📊 实时监控:提供缓存数量、状态等实时信息查询
  • 🎛️ 可配置:支持最大缓存数量、清理策略等个性化配置

🎯 场景

  • 表单页面:保持用户填写的表单数据,避免意外丢失
  • 列表页面:保持滚动位置、筛选条件、分页状态
  • 详情页面:避免重复网络请求,提升用户体验
  • 多标签页应用:实现标签页之间的快速切换和管理
  • 复杂状态管理:保持组件内部状态,如计时器、WebSocket 连接等
  • 工作流应用:保持用户在不同步骤之间的操作进度

📦 安装

npm install @feoe/react-keepalive
#
yarn add @feoe/react-keepalive
#
pnpm add @feoe/react-keepalive

🔧 API 文档

组件 API

KeepAliveOutlet

路由级别的缓存组件,用于替代 react-router-dom<Outlet>

import { KeepAliveOutlet } from '@feoe/react-keepalive';

function Layout() {
  return (
    <div>
      <nav>导航栏</nav>
      <KeepAliveOutlet 
        maxCacheSize={20} 
        cleanOnUnmount={true} 
      />
    </div>
  );
}

Props:

  • maxCacheSize?: number - 最大缓存数量,默认 20
  • cleanOnUnmount?: boolean - 组件卸载时是否清理所有缓存,默认 true
  • keepAliveRef?: React.RefObject<KeepAliveRef> - 外部传入的 KeepAlive 引用,用于在组件外部控制缓存

KeepAliveProvider

缓存上下文提供者,为子组件提供缓存管理功能。

import { KeepAliveProvider } from '@feoe/react-keepalive';

function App() {
  return (
    <KeepAliveProvider activeKey="/current-route" keepAliveRef={keepAliveRef}>
      {/* 子组件 */}
    </KeepAliveProvider>
  );
}

KeepAlive

核心缓存组件,可直接使用进行精细化缓存控制。

import { KeepAlive } from '@feoe/react-keepalive';

function CustomKeepAlive() {
  return (
    <KeepAlive activeKey="current-key" maxCacheSize={5}>
      {/* 需要缓存的内容 */}
    </KeepAlive>
  );
}

Hooks API

useActiveEffect(callback)

类似 useEffect 的生命周期管理钩子,专为 KeepAlive 组件设计。

import { useActiveEffect } from '@feoe/react-keepalive';

function MyComponent() {
  useActiveEffect(() => {
    console.log('组件被激活');
    
    // 返回清理函数(在组件失活时执行)
    return () => {
      console.log('组件失活,执行清理');
    };
  });
  
  return <div>我的组件</div>;
}

执行时机:

  • 组件首次挂载时:useEffect 执行 → useActiveEffect 执行
  • 从缓存中恢复时:useActiveEffect 执行
  • 组件失活时:useActiveEffect 返回的清理函数执行
  • 组件销毁时:useActiveEffect 返回的清理函数执行 → useEffect 的清理函数执行

useKeepAliveRef()

获取 KeepAlive 实例引用,提供完整的缓存控制方法。

import { useKeepAliveRef } from '@feoe/react-keepalive';

function CacheManager() {
  const {
    refresh,
    destroy,
    destroyAll,
    destroyOther,
    isCached,
    getCacheKeys,
    getCacheSize
  } = useKeepAliveRef();

  return (
    <div>
      <p>缓存数量: {getCacheSize()}</p>
      <button onClick={() => refresh()}>刷新当前页面</button>
      <button onClick={() => destroy()}>销毁当前缓存</button>
      <button onClick={() => destroyOther()}>销毁其他缓存</button>
      <button onClick={() => destroyAll()}>清空所有缓存</button>
    </div>
  );
}

返回方法的接口定义:

export interface KeepAliveRef {
  /**
   * 刷新指定 key 的缓存(保持组件状态)
   */
  refresh: (key?: string) => void;
  /**
   * 清理指定 key 的缓存
   */
  destroy: (key?: string | string[]) => void;
  /**
   * 清理所有除 key 之外的其他缓存
   */
  destroyOther: (key?: string) => void;
  /**
   * 清理所有缓存
   */
  destroyAll: () => void;
  /**
   * 是否已经缓存
   */
  isCached: (key?: string) => boolean;
  /**
   * 获取当前缓存的 keys
   */
  getCacheKeys: () => string[];
  /**
   * 获取缓存数量
   */
  getCacheSize: () => number;
}

useKeepAliveContext()

获取 KeepAlive 上下文,用于底层控制和扩展。

import { useKeepAliveContext } from '@feoe/react-keepalive';

function AdvancedComponent() {
  const { activeKey, keepAliveRef } = useKeepAliveContext();

  return <div>当前活跃路由: {activeKey}</div>;
}

返回方法的接口定义:

export interface IKeepAliveContext {
  activeKey: string;
  keepAliveRef: RefObject<KeepAliveRef>;
  // 注册/注销回调函数
  registerActivatedCallback: (
    key: string,
    callback: () => void,
  ) => () => void;
  registerDeactivatedCallback: (
    key: string,
    callback: () => void,
  ) => () => void;
  // 触发回调函数
  triggerActivatedCallbacks: (key: string) => void;
  triggerDeactivatedCallbacks: (key: string) => void;
}

🚀 其他功能

动态刷新缓存

const { refresh } = useKeepAliveRef();

// 刷新当前路由缓存(保持组件状态,触发重新渲染)
refresh();

// 刷新指定路由缓存
refresh('/specific-route');

检查缓存状态

const { isCached, getCacheKeys, getCacheSize } = useKeepAliveRef();

// 检查特定路由是否已缓存
if (isCached('/some-route')) {
  console.log('该路由已被缓存');
}

// 获取所有缓存的路由列表
const allCachedRoutes = getCacheKeys();
console.log('已缓存的路由:', allCachedRoutes);

// 获取当前缓存数量
const cacheCount = getCacheSize();
console.log('当前缓存数量:', cacheCount);

批量管理缓存

// 批量销毁多个缓存
const { destroy } = useKeepAliveRef();
destroy(['/route1', '/route2', '/route3']);

// 销毁除当前路由外的所有其他缓存
const { destroyOther } = useKeepAliveRef();
destroyOther(); // 不传参数则保留当前路由
destroyOther('/route-to-keep'); // 传参数则保留指定路由

📝 示例项目

查看 examples/keep-alive-transition 目录中的完整示例,包含:

  • 基础 KeepAlive 用法演示
  • 表单状态保持示例
  • 列表滚动位置保持
  • 缓存管理功能展示
  • 生命周期钩子使用方法
  • 批量操作和高级功能演示

运行示例:

cd examples/keep-alive-transition
npm install
npm run dev

🎯 快速开始

1. 基础路由缓存

最简单的使用方式,替换 <Outlet><KeepAliveOutlet>

import { KeepAliveOutlet } from '@feoe/react-keepalive';
import { createBrowserRouter, RouterProvider, Link } from 'react-router-dom';

const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      { path: 'home', element: <HomePage /> },
      { path: 'about', element: <AboutPage /> },
      { path: 'contact', element: <ContactPage /> },
    ],
  },
]);

function Layout() {
  return (
    <div>
      <nav>
        <Link to="/home">首页</Link>
        <Link to="/about">关于</Link>
        <Link to="/contact">联系</Link>
      </nav>
      {/* 使用 KeepAliveOutlet 替代 Outlet */}
      <KeepAliveOutlet maxCacheSize={20} />
    </div>
  );
}

function App() {
  return <RouterProvider router={router} />;
}

2. 生命周期管理

在需要缓存的页面组件中使用 useActiveEffect

import { useActiveEffect, useKeepAliveRef } from '@feoe/react-keepalive';
import { useState, useRef } from 'react';

function FormPage() {
  const [formData, setFormData] = useState({ name: '', email: '' });
  const [count, setCount] = useState(0);
  const timerRef = useRef<NodeJS.Timeout>();
  const { isCached } = useKeepAliveRef();

  useActiveEffect(() => {
    console.log('页面激活,开始计时器');
    
    // 启动计时器
    timerRef.current = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
    
    // 返回清理函数
    return () => {
      console.log('页面失活,清理计时器');
      if (timerRef.current) {
        clearInterval(timerRef.current);
      }
    };
  });

  return (
    <div>
      <h2>表单页面</h2>
      <p>是否已缓存: {isCached() ? '是' : '否'}</p>
      <p>计数器: {count} (激活时递增,失活时停止)</p>
      
      <form>
        <input
          type="text"
          placeholder="姓名"
          value={formData.name}
          onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
        />
        <input
          type="email"
          placeholder="邮箱"
          value={formData.email}
          onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
        />
      </form>
    </div>
  );
}

3. 缓存管理

创建一个缓存管理页面:

import { useActiveEffect, useKeepAliveRef } from '@feoe/react-keepalive';
import { useState } from 'react';

function CacheManagerPage() {
  const [cacheList, setCacheList] = useState<string[]>([]);
  const { 
    getCacheKeys, 
    getCacheSize, 
    refresh, 
    destroy, 
    destroyAll,
    destroyOther,
    isCached
  } = useKeepAliveRef();

  const refreshCacheList = () => {
    setCacheList(getCacheKeys());
  };

  useActiveEffect(() => {
    console.log('缓存管理页面激活');
    refreshCacheList();
    
    return () => {
      console.log('缓存管理页面失活');
    };
  });

  return (
    <div>
      <h2>缓存管理中心</h2>
      <p>总缓存数量: {getCacheSize()}</p>
      
      <div style={{ marginBottom: '16px' }}>
        <button onClick={refreshCacheList} style={{ marginRight: '8px' }}>
          刷新列表
        </button>
        <button onClick={() => destroyAll()} style={{ marginRight: '8px' }}>
          清空所有缓存
        </button>
        <button onClick={() => destroyOther()}>
          销毁其他缓存
        </button>
      </div>
      
      <h3>缓存列表:</h3>
      {cacheList.map(key => (
        <div key={key} style={{ 
          margin: '8px 0', 
          padding: '8px', 
          border: '1px solid #ccc',
          borderRadius: '4px'
        }}>
          <span style={{ marginRight: '8px' }}>{key}</span>
          <span style={{ 
            color: isCached(key) ? 'green' : 'gray',
            marginRight: '8px'
          }}>
            {isCached(key) ? '已缓存' : '未缓存'}
          </span>
          <button onClick={() => refresh(key)} style={{ marginRight: '8px' }}>
            刷新
          </button>
          <button onClick={() => destroy(key)}>
            删除
          </button>
        </div>
      ))}
      
      {/* 批量操作示例 */}
      <div style={{ marginTop: '16px' }}>
        <h4>批量操作:</h4>
        <button onClick={() => destroy(cacheList.slice(0, 2))}>
          删除前两个缓存
        </button>
      </div>
    </div>
  );
}

4. 外部控制缓存

在某些场景下,您可能需要在 KeepAliveOutlet 外部控制缓存,比如实现 Tabs 功能:

import { KeepAliveOutlet, KeepAliveRef } from '@feoe/react-keepalive';
import { useRef } from 'react';

function TabsLayout() {
  const keepAliveRef = useRef<KeepAliveRef>(null);
  
  const handleCloseTab = (tabKey: string) => {
    // 在外部控制缓存销毁
    keepAliveRef.current?.destroy(tabKey);
  };
  
  const handleCloseOtherTabs = (activeKey: string) => {
    // 销毁除当前标签外的其他缓存
    keepAliveRef.current?.destroyOther(activeKey);
  };

  return (
    <div>
      <div className="tabs">
        {/* 标签页头部 */}
        <button onClick={() => handleCloseTab('/some-route')}>
          关闭标签
        </button>
        <button onClick={() => handleCloseOtherTabs('/current-route')}>
          关闭其他标签
        </button>
      </div>
      
      {/* 传入外部的 keepAliveRef */}
      <KeepAliveOutlet 
        keepAliveRef={keepAliveRef}
        maxCacheSize={20}
      />
    </div>
  );
}

🤝 贡献指南

欢迎提交 Issue 和 Pull Request!

  1. Fork 本仓库
  2. 创建功能分支:git checkout -b feature/new-feature
  3. 提交更改:git commit -am 'Add new feature'
  4. 推送分支:git push origin feature/new-feature
  5. 提交 Pull Request

📄 许可证

MIT License - 详见 LICENSE 文件

About

Not based on React.Activity, but implementation is same like it based on Suspense.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •