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 同上

相关推荐
RPGMZ1 小时前
RPGMZ游戏引擎之如何设计每小时开启一次的副本
javascript·游戏·游戏引擎·rpgmz
RPGMZ1 小时前
RPGMZ游戏引擎 如何手动控制文字显示速度
开发语言·javascript·游戏引擎·rpgmz
阿星做前端1 小时前
一个倒计时功能引发的线上故障
前端·javascript·react.js
tianzhiyi1989sq2 小时前
Vue框架深度解析:从Vue2到Vue3的技术演进与实践指南
前端·javascript·vue.js
我是来人间凑数的2 小时前
electron 配置特定文件右键打开
前端·javascript·electron
安心不心安2 小时前
React封装框架dvajs(状态管理+异步操作+数据订阅等)
前端·react.js·前端框架
未来之窗软件服务3 小时前
js调用微信支付 第二步 获取access_token ——仙盟创梦IDE
开发语言·javascript·微信·微信支付·仙盟创梦ide·东方仙盟
可可格子衫3 小时前
keep-alive缓存文章列表案例完整代码(Vue2)
vue.js·缓存
洛小豆3 小时前
为什么可以通过域名访问接口,但不能通过IP地址访问接口?
前端·javascript·vue.js
码农开荒路3 小时前
Redis之缓存一致性
数据库·redis·缓存