React中实现keepalive组件缓存效果
时间:2023-01-13
本文章向大家介绍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组件已经完成啦~
脚踏实地行,海阔天空飞
原文地址:https://www.cnblogs.com/coder--wang/p/17049408.html
- 每周学点大数据 | No.3算法设计与分析理论
- HDU 1874 畅通工程续【Floyd算法实现】
- 接口测试 | 21 基于flask弄个restful API服务出来
- 数论部分第二节:埃拉托斯特尼筛法 埃拉托斯特尼筛法
- [接口测试 -基础篇] 20 用flask写一个简单server用于接口测试
- 接口测试 | urllib篇 19 urllib基本示例
- 接口测试 | urllib篇 18 urllib介绍
- 【专知-Deeplearning4j深度学习教程01】分布式Java开源深度学习框架DL4j安装使用: 图文+代码
- .Net Core Runtime安装说明
- 【专知-Deeplearning4j深度学习教程02】用ND4J自己动手实现RBM: 图文+代码
- 【专知-Deeplearning4j深度学习教程03】使用多层神经网络分类MNIST数据集:图文+代码
- TypeScript 1.6发布:完全支持React/JSX
- 【专知-Java Deeplearning4j深度学习教程04】使用CNN进行文本分类:图文+代码
- sql server之数据库语句优化
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法