react-activation 组件级缓存解决方案

文章目录

  • [一、KeepAlive 组件](#一、KeepAlive 组件)
  • [二、AliveScope 容器](#二、AliveScope 容器)
  • [三、useAliveController Hook](#三、useAliveController Hook)
  • 四、生命周期
  • 五、完整示例

react-activation 主要解决 React 项目中的「页面缓存」需求(是第三方库,非React 官方),类似于 Vue 中的 <KeepAlive>:

功能 说明
<KeepAlive> 组件 缓存组件 DOM,不卸载
<AliveScope> 容器 缓存容器,必须包裹在外层
useAliveController Hook 提供缓存管理 API(如 drop、refresh)
useActivate / useUnactivate 生命周期 激活与失活钩子

一、KeepAlive 组件

<KeepAlive> 是一个高阶组件,用于缓存(keep-alive)React 组件的状态,类似于 Vue 的 ,在组件卸载后保留其状态和 DOM,提升体验(例如表单不丢失、滚动位置保留等)。

用于缓存组件的 UI 和状态 ,当 <AdminHome /> 页面切走再回来,状态不会丢失,组件不会重新挂载。

html 复制代码
<KeepAlive id="admin-home">
	<AdminHome />
</KeepAlive>

基本属性

属性 类型 默认值 说明
id string - 缓存唯一标识 ,必须唯一,一般用 pathname
name string 同 id 可选的缓存名称(某些缓存操作可以用 name)
when boolean true 是否启用缓存,设为 false 表示不缓存
saveScrollPosition `false 'screen' 'container'
autoFreeze boolean true 切换时是否冻结 DOM,节省资源
extra any undefined 附加信息,可在缓存控制器中使用(不常用)

二、AliveScope 容器

<AliveScope> 是 react-activation 提供的作用域容器,用来管理缓存组件的上下文和分组控制。

  • 提供上下文,让 KeepAlive 可以记录并复用组件状态

  • 管理组件缓存生命周期

  • 可用于分组销毁缓存(配合 dropScope(scopeId))

  1. <AliveScope> 是必须包裹住所有 <KeepAlive> 的组件,否则 KeepAlive 不会起作用。

    如果不包裹 <KeepAlive>,它内部就无法访问缓存管理上下文:

    • KeepAlive 会直接按普通组件渲染,不会缓存
    • useActivate / useUnactivate 钩子不会被调用
    • useAliveController() 获取到的控制器是空的
    html 复制代码
    <AliveScope name="user-scope">
      <KeepAlive id="user-list"><UserList /></KeepAlive>
      <KeepAlive id="user-detail"><UserDetail /></KeepAlive>
    </AliveScope>

    标准写法是直接把 <AliveScope> 放在根节点。

    js 复制代码
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { AliveScope } from 'react-activation';
    import App from './App';
    
    ReactDOM.render(
      <AliveScope>
        <App />
      </AliveScope>,
      document.getElementById('root')
    );
  2. AliveScope 可选属性

    属性 类型 默认值 说明
    name string default 作用域唯一标识,可用于区分多个 AliveScope
    include string[] [] 允许缓存的组件 ID 列表(白名单)
    exclude string[] [] 禁止缓存的组件 ID 列表(黑名单)
    max number Infinity 缓存数量上限,超过后会自动淘汰最久未使用的(LRU 策略)
    cacheKey string 同 name 与 localStorage 缓存相关的 key(需要搭配 persist)
    persist boolean false 是否持久化缓存(刷新页面或关闭浏览器后,再次进入,缓存状态仍然保留,保存在 localStorage)
    autoClear boolean false 是否在页面刷新后自动清空缓存(防止缓存穿透,防止过时数据)
  3. 配合 KeepAlive 使用

    js 复制代码
    import { AliveScope, KeepAlive } from 'react-activation';
    import UserList from './UserList';
    import UserDetail from './UserDetail';
    
    export default function Root() {
      return (
        <AliveScope name="user-scope" include={['user-list']}>
    		<KeepAlive id="user-list"><UserList /></KeepAlive>
    		<KeepAlive id="user-detail"><UserDetail /></KeepAlive>
        </AliveScope>
      );
    }
    • 只有 user-list 会被缓存(受 include 控制)

    • 可以通过 useAliveController().dropScope('user-scope') 一键清除

  4. 多个 AliveScope 的使用方式

    把用户模块和管理员模块的缓存完全隔离开,这样每个作用域有自己独立的缓存池

    html 复制代码
    <!-- 用户模块 -->
    <AliveScope name="user-scope">
      <KeepAlive id="user-list"><UserList /></KeepAlive>
      <KeepAlive id="user-detail"><UserDetail /></KeepAlive>
    </AliveScope>
    
    <!-- 管理员模块 -->
    <AliveScope name="admin-scope">
      <KeepAlive id="admin-home"><AdminHome /></KeepAlive>
    </AliveScope>

    这时可以使用 useAliveController() 来获取缓存控制器,跨作用域控制:

    js 复制代码
    import { useAliveController } from 'react-activation';
    
    export default function ClearButton() {
      const { dropScope } = useAliveController();
    
      return (
        <>
          <button onClick={() => dropScope('user-scope')}>清空用户模块缓存</button>
          <button onClick={() => dropScope('admin-scope')}>清空管理员模块缓存</button>
        </>
      );
    }
    • dropScope('user-scope') 会销毁 user-scope 作用域中所有 KeepAlive 缓存

    • 也可以用 refreshScope(name) 强制刷新一个作用域内所有组件


三、useAliveController Hook

这是一个自定义 Hook,提供对缓存组件的控制,比如手动刷新(drop)某个缓存组件、获取缓存状态等。

js 复制代码
const { drop, dropScope, refresh } = useAliveController();

// 单作用域: 卸载某个缓存组件(通过 KeepAlive 的 id)
// 使用场景:点击"关闭标签页"
drop('user-list');  // 卸载 id 为 'user-list' 的 KeepAlive 缓存
drop('user-detail');
// 👉 全部要写一遍,或维护复杂缓存 id 列表


// 多作用域: 卸载该作用域下的所有缓存组件(通过 AliveScope 的 name),比 drop(id) 更高级别的操作
// 使用场景:退出登录清除所有缓存
dropScope('user-scope'); // 卸载 <AliveScope name="user-scope"> 下的所有 KeepAlive 缓存


// 强制刷新(先卸载再重建)
// 使用场景:点击"重置表单"或"刷新页面"
refresh('user-list'); // 会先 drop('user-list'),然后立刻重新挂载组件
  • dropScope 的参数是 中的 name。

  • 使用前确保这些组件确实是包裹在 <AliveScope> 内的。

  • AliveScope 是 react-activation 中用于分组缓存的容器,必须明确设置 name 才能使用 dropScope。

    .

🔥 在 react-activation 中,组件必须处于「非激活状态」( 即 KeepAlive 的 when 为 false、或组件被隐藏 ),才允许卸载或刷新。不会立即卸载当前正在显示的组件

方法名 作用说明 是否会立即卸载正在显示的组件 何时真正卸载组件
drop(name) 手动卸载指定缓存的组件(通过 name 标识) ❌ 否 在组件被切换(即不再显示)后才真正卸载
dropScope(scope) 卸载一整个作用域(scope 对应 <KeepAlive name="xxx" scope="xxx" />)中的所有组件 ❌ 否 同上:仅在这些组件不再显示时卸载
refresh(name) 重新渲染指定组件(会先卸载再重新 mount) ❌ 否 当前显示的组件会重建,但会保留在界面中刷新(不是立即卸载)
refreshScope(scope) 刷新某个 scope 下的所有组件 ❌ 否 当前显示的组件会重建(看起来是刷新)
clear() 清空所有缓存组件 ❌ 否 当前显示的组件仍保留,其他会被清空(切走后卸载)
getCachingNodes() 获取当前缓存中的组件节点列表 - -
  • drop / dropScope / refresh / refreshScope 不会卸载当前正在显示的组件

  • 它们只对非激活(未渲染)的组件生效

  • ✅ 正确的做法是:切换走 → drop → 才生效:

    js 复制代码
    history.push('/other');
    await drop('/current'); // ✅ 现在它处于非激活状态,drop 生效

四、生命周期

react-activation (React 第三方库) 提供的自定义 Hook,模拟 Vue 的 activated / deactivated 生命周期。

js 复制代码
<AliveScope>
  <KeepAlive id="user">
    <UserPage />
  </KeepAlive>
</AliveScope>


import { useActivate, useUnactivate, useAliveEffect } from 'react-activation';

// 组件激活时调用(进入或返回该缓存组件时),替代 useEffect 的 didShow
useActivate(() => {
  console.log('页面被激活(显示): 进入时刷新数据')
});

// 组件失活时调用(从该组件跳出,但未卸载),类似 componentWillPause
useUnactivate(() => {
  console.log('页面被隐藏但未卸载: 退出时保存状态')
});

// 只有当组件是"激活状态"时,才会执行 useEffect,可以响应 deps 的变化,可替代 useEffect + useActivate 组合
useAliveEffect(() => {
  const timer = setInterval(() => {
    console.log('只在激活状态时轮询');
  }, 1000);

  return () => clearInterval(timer);
}, []);

类似于 Vue3:

js 复制代码
<template>
  <KeepAlive include="UserPage">
    <component :is="currentView" />
  </KeepAlive>
</template>


// 原生生命周期钩子
onActivated(() => {
  console.log('组件被缓存激活');
});

onDeactivated(() => {
  console.log('组件被缓存关闭');
});

五、完整示例

✅ 标签切换自动缓存

✅ 点击关闭标签页 → 销毁对应缓存

✅ 支持多个 AliveScope 管理模块分组

✅ 使用 KeepAlive + useActivate + useUnactivate

  1. main.tsx(注册多个作用域)

    js 复制代码
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { AliveScope } from 'react-activation';
    import App from './App';
    
    ReactDOM.render(
      <>
        <AliveScope name="module-user">
          <App />
        </AliveScope>
        {/* 可拓展其他模块作用域 */}
      </>,
      document.getElementById('root')
    );
  2. App.tsx(入口,渲染标签页)

    js 复制代码
    import React from 'react';
    import TabView from './components/TabView';
    
    export default function App() {
      return (
        <div>
          <TabView />
        </div>
      );
    }
  3. TabView.tsx(核心组件)

    js 复制代码
    import React, { useState } from 'react';
    import { KeepAlive, useAliveController } from 'react-activation';
    import PageA from './PageA';
    import PageB from './PageB';
    import PageC from './PageC';
    
    const tabComponents: Record<string, React.ReactNode> = {
      A: <PageA />,
      B: <PageB />,
      C: <PageC />,
    };
    
    const TabView = () => {
      const [tabs, setTabs] = useState(['A', 'B', 'C']);
      const [active, setActive] = useState('A');
    
      const { drop } = useAliveController();
    
      const closeTab = async (key: string) => {
      	// 比如当前 tabs 是 ['A', 'B', 'C'],要关闭 A 标签
        setTabs(prev => prev.filter(t => t !== key)); // 更新标签页列表(异步),由['A', 'B', 'C'] -> ['B', 'C']
        if (active === key) { // 如果关闭的是当前激活标签
          const other = tabs.find(t => t !== key); // 从标签页列表['A', 'B', 'C']中找出第一个非 key 的 tab(即 'B')
          if (other) setActive(other); // 激活新标签B
        }
        await drop(`page-${key}`); // 卸载对应标签的缓存组件(卸载'page-A')
      };
    
      return (
        <div>
          <div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
            {tabs.map(tab => (
              <div
                key={tab}
                style={{
                  padding: '6px 12px',
                  border: active === tab ? '2px solid blue' : '1px solid #ccc',
                  borderRadius: 4,
                  cursor: 'pointer',
                  background: '#f7f7f7',
                  position: 'relative',
                }}
                onClick={() => setActive(tab)}
              >
                Page {tab}
                <span
                  onClick={e => {
                    e.stopPropagation();
                    closeTab(tab);
                  }}
                  style={{
                    marginLeft: 6,
                    color: 'red',
                    cursor: 'pointer',
                    fontWeight: 'bold',
                  }}
                >
                  ×
                </span>
              </div>
            ))}
          </div>
    
          <div style={{ border: '1px solid #ccc', padding: 12 }}>
            {tabs.map(tab =>
              active === tab ? (
                <KeepAlive id={`page-${tab}`} key={tab}>
                  {tabComponents[tab]}
                </KeepAlive>
              ) : null
            )}
          </div>
        </div>
      );
    };
    
    export default TabView;
  4. PageA.tsx(缓存与生命周期)

    js 复制代码
    import React, { useState } from 'react';
    import { useActivate, useUnactivate } from 'react-activation';
    
    export default function PageA() {
      const [count, setCount] = useState(0);
    
      useActivate(() => {
        console.log('PageA 激活');
      });
    
      useUnactivate(() => {
        console.log('PageA 失活');
      });
    
      return (
        <div>
          <h2>Page A</h2>
          <p>计数: {count}</p>
          <button onClick={() => setCount(c => c + 1)}>+1</button>
        </div>
      );
    }

    PageB.tsx、PageC.tsx 同上

相关推荐
优选资源分享24 分钟前
PicGo v3.0.3 新版 | 开源免费图床工具
javascript·笔记·electron
ShineWinsu1 小时前
对于JavaScript:JavaScript基础语法的解析
开发语言·前端·javascript·面试·web·基础语法·循环
普通人62 小时前
php+html+canvas实现树状打开的结构
开发语言·javascript·html5·1024程序员节
猫头_3 小时前
AI 流式传输工程指南:模型吐出半截 JSON,前端如何边收边渲染?
前端·javascript·llm
鶴哥只手遮天4 小时前
React 19.3.1 源码深度剖析-第 12 章(下)RSC 与 Flight 协议
前端·react.js·开源
我的div丢了肿么办4 小时前
element-plus表格使用:filter-method过滤导致出现无数据
前端·javascript·vue.js
YIAN4 小时前
入门篇:LangGraph 入门实战:从零搭建最简单的 RAG 问答系统
javascript·langchain
YIAN4 小时前
高级篇:进阶多跳 RAG:子问题拆解 + 循环检索 + 智能规划,搞定复杂链式问题
javascript·langchain
YIAN5 小时前
前置篇:手把手用 Milvus + LangChain 搭建小说向量知识库,《天龙八部》EPUB 一键入库
javascript·langchain
YIAN5 小时前
进阶篇:RAG 优化第一步:给你的问答系统加上智能路由,实现双分支分流
javascript·langchain