React中实现keepalive组件缓存效果
背景:由于react官方并没有提供缓存组件相关的api(类似vue中的keepalive),在某些场景,会使得页面交互性变的很差,比如在有搜索条件的表格页面,点击某一条数据跳转到详情页面,再返回表格页面,会重新请求数据,搜索条件也将清空,用户得重新输入搜索条件,再次请求数据,大大降低办公效率,如图:

目标:封装keepalive缓存组件,实现组件的缓存,并暴露相关方法,可以手动清除缓存。
版本:React 17,react-router-dom 5
结构:

代码:
cache-types.js
// 缓存状态
export const CREATE = 'CREATE'; // 创建
export const CREATED = 'CREATED'; // 创建成功
export const ACTIVE = 'ACTIVE'; // 激活
export const DESTROY = 'DESTROY'; // 销毁
CacheContext.js
import React from 'react';
const CacheContext = React.createContext();
export default CacheContext;
KeepAliveProvider.js
1 import React, { useReducer, useCallback } from "react";
2 import CacheContext from "./CacheContext";
3 import cacheReducer from "./cacheReducer";
4 import * as cacheTypes from "./cache-types";
5 function KeepAliveProvider(props) {
6 let [cacheStates, dispatch] = useReducer(cacheReducer, {});
7 const mount = useCallback(
8 ({ cacheId, element }) => {
9 // 挂载元素方法,提供子组件调用挂载元素
10 if (cacheStates[cacheId]) {
11 let cacheState = cacheStates[cacheId];
12 if (cacheState.status === cacheTypes.DESTROY) {
13 let doms = cacheState.doms;
14 doms.forEach((dom) => dom.parentNode.removeChild(dom));
15 dispatch({ type: cacheTypes.CREATE, payload: { cacheId, element } }); // 创建缓存
16 }
17 } else {
18 dispatch({ type: cacheTypes.CREATE, payload: { cacheId, element } }); // 创建缓存
19 }
20 },
21 [cacheStates]
22 );
23 let handleScroll = useCallback(
24 // 缓存滚动条
25 (cacheId, { target }) => {
26 if (cacheStates[cacheId]) {
27 let scrolls = cacheStates[cacheId].scrolls;
28 scrolls[target] = target.scrollTop;
29 }
30 },
31 [cacheStates]
32 );
33 return (
34 <CacheContext.Provider
35 value={{ mount, cacheStates, dispatch, handleScroll }}
36 >
37 {props.children}
38 {/* cacheStates维护所有缓存信息, dispatch派发修改缓存状态*/}
39 {Object.values(cacheStates)
40 .filter((cacheState) => cacheState.status !== cacheTypes.DESTROY)
41 .map(({ cacheId, element }) => (
42 <div
43 id={`cache_${cacheId}`}
44 key={cacheId}
45 // 原生div中声明ref,当div渲染到页面,会执行ref中的回调函数,这里在id为cache_${cacheId}的div渲染完成后,会继续渲染子元素
46 ref={(dom) => {
47 let cacheState = cacheStates[cacheId];
48 if (
49 dom &&
50 (!cacheState.doms || cacheState.status === cacheTypes.DESTROY)
51 ) {
52 let doms = Array.from(dom.childNodes);
53 dispatch({
54 type: cacheTypes.CREATED,
55 payload: { cacheId, doms },
56 });
57 }
58 }}
59 >
60 {element}
61 </div>
62 ))}
63 </CacheContext.Provider>
64 );
65 }
66 const useCacheContext = () => {
67 const context = React.useContext(CacheContext);
68 if (!context) {
69 throw new Error("useCacheContext必须在Provider中使用");
70 }
71 return context;
72 };
73 export { KeepAliveProvider, useCacheContext };
withKeepAlive.js
1 import React, { useContext, useRef, useEffect } from "react";
2 import CacheContext from "./CacheContext";
3 import * as cacheTypes from "./cache-types";
4 function withKeepAlive(
5 OldComponent,
6 { cacheId = window.location.pathname, scroll = false }
7 ) {
8 return function (props) {
9 const { mount, cacheStates, dispatch, handleScroll } =
10 useContext(CacheContext);
11 const ref = useRef(null);
12 useEffect(() => {
13 if (scroll) {
14 // scroll = true, 监听缓存组件的滚动事件,调用handleScroll()缓存滚动条
15 ref.current.addEventListener(
16 "scroll",
17 handleScroll.bind(null, cacheId),
18 true
19 );
20 }
21 }, [handleScroll]);
22 useEffect(() => {
23 let cacheState = cacheStates[cacheId];
24 if (
25 cacheState &&
26 cacheState.doms &&
27 cacheState.status !== cacheTypes.DESTROY
28 ) {
29 // 如果真实dom已经存在,且状态不是DESTROY,则用当前的真实dom
30 let doms = cacheState.doms;
31 doms.forEach((dom) => ref.current.appendChild(dom));
32 if (scroll) {
33 // 如果scroll = true, 则将缓存中的scrollTop拿出来赋值给当前dom
34 doms.forEach((dom) => {
35 if (cacheState.scrolls[dom])
36 dom.scrollTop = cacheState.scrolls[dom];
37 });
38 }
39 } else {
40 // 如果还没产生真实dom,派发生成
41 mount({
42 cacheId,
43 element: <OldComponent {...props} dispatch={dispatch} />,
44 });
45 }
46 }, [cacheStates, dispatch, mount, props]);
47 return <div id={`keepalive_${cacheId}`} ref={ref} />;
48 };
49 }
50 export default withKeepAlive;
index.js
export { KeepAliveProvider } from "./KeepAliveProvider";
export {default as withKeepAlive} from './withKeepAlive';
使用:
1.用<KeepAliveProvider></KeepAliveProvider>将目标缓存组件或者父级包裹;
2.将需要缓存的组件,传入withKeepAlive方法中,该方法返回一个缓存组件;
3.使用该组件;
App.js
1 import React from "react";
2 import {
3 BrowserRouter,
4 Link,
5 Route,
6 Switch,
7 } from "react-router-dom";
8 import Home from "./Home.js";
9 import List from "./List.js";
10 import Detail from "./Detail.js";
11 import { KeepAliveProvider, withKeepAlive } from "./keepalive-cpn";
12
13 const KeepAliveList = withKeepAlive(List, { cacheId: "list", scroll: true });
14
15 function App() {
16 return (
17 <KeepAliveProvider>
18 <BrowserRouter>
19 <ul>
20 <li>
21 <Link to="/">首页</Link>
22 </li>
23 <li>
24 <Link to="/list">列表页</Link>
25 </li>
26 <li>
27 <Link to="/detail">详情页A</Link>
28 </li>
29 </ul>
30 <Switch>
31 <Route path="/" component={Home} exact></Route>
32 <Route path="/list" component={KeepAliveList}></Route>
33 <Route path="/detail" component={Detail}></Route>
34 </Switch>
35 </BrowserRouter>
36 </KeepAliveProvider>
37 );
38 }
39
40 export default App;
效果:

假设有个需求,从首页到列表页,需要清空搜索条件,重新请求数据,即回到首页,需要清除列表页的缓存。
上面的KeepAliveProvider.js中,暴露了一个useCacheContext()的hook,该hook返回了缓存组件相关数据和方法,这里可以用于清除缓存:
Home.js
1 import React, { useEffect } from "react";
2 import { DESTROY } from "./keepalive-cpn/cache-types";
3 import { useCacheContext } from "./keepalive-cpn/KeepAliveProvider";
4
5 const Home = () => {
6 const { cacheStates, dispatch } = useCacheContext();
7
8 const clearCache = () => {
9 if (cacheStates && dispatch) {
10 for (let key in cacheStates) {
11 if (key === "list") {
12 dispatch({ type: DESTROY, payload: { cacheId: key } });
13 }
14 }
15 }
16 };
17 useEffect(() => {
18 clearCache();
19 // eslint-disable-next-line
20 }, []);
21 return (
22 <div>
23 <div>首页</div>
24 </div>
25 );
26 };
27
28 export default Home;
效果:

至此,react简易版的keepalive组件已经完成啦~
脚踏实地行,海阔天空飞
React中实现keepalive组件缓存效果的更多相关文章
- vue中使用keepAlive组件缓存遇到的坑
项目开发中在用户由分类页category进入detail需保存用户状态,查阅了Vue官网后,发现vue2.0提供了一个keep-alive组件. 上一篇讲了keep-alive的基本用法,现在说说遇到 ...
- React 中的 定义组件的 两种方式
React 中创建 Components 的方式有两种:Function and Class 定义一个组件最简单的方法就是写一个 JavaScript 函数 function Welcome(prop ...
- react中使用截图组件Cropper组件
--最近项目用react,学习react并使用cropper组件裁剪图片. (这里开发组件不够统一有用tsx(TypeScript + xml/html)写的组件,有用jsx(javascript+x ...
- React中的通讯组件
1.父传子: 传递:当子组件在父组件中当做标签使用的时候,给当前子组件绑定一个自定义属性,值为需要传递的数据 接收:在子组件内部通过this.props进行接收 2.子传父 传 ...
- react中如何使用动画效果
在react中想要加入动画效果 需要引入 import {CSSTransitionGroup} from 'react-transition-group' //加入react 动画包 import ...
- React中的高阶组件
高阶组件(HOC, High-Order Component)是React中用于重组组件逻辑的高级技术,是一种编程模式而不是React的api. 直观来讲,高阶组件是以某一组件作为参数返回一个新组件的 ...
- React中使用CSSTransitionGroup插件实现轮播图
动画效果,是一个页面上必不可少的功能,学习一个新的东西,当然就要学习,如何用新的东西,用它的方法去实现以前的东西啦.今天呢,我就在这里介绍一个试用react-addons-css-transition ...
- 初学React:定义一个组件
接着聊React,今天说说如何创建一个组件类. <!DOCTYPE html> <html lang="en"> <head> <meta ...
- 前端笔记之React(二)组件内部State&React实战&表单元素的受控
一.组件内部的State 1.1 state state叫状态,是每一个类式组件都有的属性,但函数式组件,没有state. state是一个对象,什么值都可以定义. 在任何类式组件的构造函数中,可以用 ...
- 从 Vue 的视角学 React(四)—— 组件传参
组件化开发的时候,参数传递是非常关键的环节 哪些参数放在组件内部管理,哪些参数由父组件传入,哪些状态需要反馈给父组件,都需要在设计组件的时候想清楚 但实现这些交互的基础,是明白组件之间参数传递的方式, ...
随机推荐
- 微服务架构学习与思考(10):微服务网关和开源 API 网关01-以 Nginx 为基础的 API 网关详细介绍
微服务架构学习与思考(10):微服务网关和开源 API 网关01-以 Nginx 为基础的 API 网关详细介绍 一.为什么会有 API Gateway 网关 随着微服务架构的流行,很多公司把原有的单 ...
- 基于win11安装Java11环境
下载JDK解压版本 下载后解压,并放到一个没有中文路径的目录,如图所示: 配置Java环境变量 以Windows11系统为例,打开设置,搜索"环境",点击"编辑系统环境变 ...
- Java模拟生产者-消费者问题。生产者不断的往仓库中存放产品,消费者从仓库中消费产品。其中生产者和消费者都可以有若干个。在这里,生产者是一个线程,消费者是一个线程。仓库容量有限,只有库满时生产者不能存
需求分析:生产者生产产品,存放在仓库里,消费者从仓库里消费产品. 程序分析: 1.生产者仅仅在仓储未满时候生产,仓满则停止生产. 2.消费者仅仅在仓储有产品时候才能消费,仓空则等待. 3.当消费者发现 ...
- 对于python中“FileNotFoundError: [Errno 2] No such file or directory”的解决办法
在我们使用vscode运行Python代码时遇到的情况 一.出现原因:这里是由于Vscode中,python里的路径是相对与工作目录来进行定位的.所以在多级目录情况下,若不设置绝对路径,往往找不到相应 ...
- HPL Study 2
1.并行编程 (1)并行程序的逻辑: 1)将当前问题划分为多个子任务 2)考虑任务间所需要的通信通道 3)将任务聚合成复合任务 4)将复合任务分配到核上 (2)共享内存编程: 路障 ----> ...
- 【ASP.NET Core】MVC控制器的各种自定义:应用程序约定的接口与模型
从本篇起,老周会连发N篇水文,总结一下在 MVC 项目中控制器的各种自定义配置. 本文内容相对轻松,重点讨论一下 MVC 项目中的各种约定接口.毕竟你要对控制器做各种自定义时,多数情况会涉及到约定接口 ...
- selenium被某些网页检测不允许正常访问、登录等,解决办法
网站通过什么方式检测 function b() { return "$cdc_asdjflasutopfhvcZLmcfl_"in u || d.webdriver } 通过上方的 ...
- scrapy框架命令
scrapy startproject #创建scrapy项目 scrapy genspider test www.baidu.com #在项目下的spider目录下生成爬虫文件 test爬虫名称 w ...
- KafkaOffsetMonitor:监控消费者和延迟的队列
个人名片: 因为云计算成为了监控工程师 个人博客:念舒_C.ying CSDN主页️:念舒_C.ying 目录 消费者组列表 消费组的topic列表 图中参数含义解释如下: topic的历史位置 O ...
- 【云原生 · Kubernetes】KubeVirt热迁移
[云原生 · Kubernetes]KubeVirt热迁移 检查节点和kubevirt状态 启用热迁移 创建虚拟机 在虚拟机上启动一个服务 迁移虚拟机 热迁移是KubeVirt支持的一个常见虚拟化特性 ...