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

文章目录

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

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

功能 说明
<KeepAlive> 组件 缓存组件 DOM,不卸载
<AliveScope> 容器 缓存容器,必须包裹在外层
useAliveController Hook 提供缓存管理 API(如 droprefresh
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 同上

相关推荐
姜太小白7 分钟前
【ECharts】多个ECharts版本共存解决方案
前端·javascript·echarts
前端风云志10 分钟前
JavaScript面试题,为什么[] + 0 = '0', 而{} + 0 = 0?
javascript
FogLetter21 分钟前
节流(Throttle):给频繁触发的事件装上"冷却时间"
前端·javascript
小公主23 分钟前
彻底搞懂 Event Loop!这篇文章帮你一次性吃透宏任务、微任务、执行顺序
前端·javascript
搞前端的小菜40 分钟前
从零实现一个GPT 【React + Express】--- 【4】实现文生图的功能
gpt·react.js·express
EndingCoder1 小时前
排序算法与前端交互优化
开发语言·前端·javascript·算法·排序算法·交互
晓13131 小时前
JavaScript加强篇——第五章 DOM节点(加强)与BOM
java·开发语言·javascript
千宇宙航1 小时前
闲庭信步使用图像验证平台加速FPGA的开发:第九课——图像插值的FPGA实现
图像处理·计算机视觉·缓存·fpga开发
三月的一天1 小时前
在 React Three Fiber 中实现 3D 模型点击扩散波效果
前端·react.js·前端框架
DoraBigHead1 小时前
🧠【彻底读懂 reduce】acc 是谁?我是谁?我们要干嘛?
前端·javascript·面试