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;
cacheReducer.js
import * as cacheTypes from "./cache-types";
function cacheReducer(cacheStates = {}, { type, payload }) {switch (type) {case cacheTypes.CREATE:return {...cacheStates,[payload.cacheId]: {scrolls: {}, // 缓存滚动条,key: dom, value: scrollTopcacheId: payload.cacheId, // 缓存Idelement: payload.element, // 需要渲染的虚拟DOMdoms: undefined, // 当前的虚拟dom所对应的真实domstatus: cacheTypes.CREATE,// 缓存状态},};case cacheTypes.CREATED:return {...cacheStates,[payload.cacheId]: {...cacheStates[payload.cacheId],doms: payload.doms,status: cacheTypes.CREATED,},};case cacheTypes.ACTIVE:return {...cacheStates,[payload.cacheId]: {...cacheStates[payload.cacheId],status: cacheTypes.ACTIVE,},};case cacheTypes.DESTROY:return {...cacheStates,[payload.cacheId]: {...cacheStates[payload.cacheId],status: cacheTypes.DESTROY,},};default:return cacheStates;}
}
export default cacheReducer;
KeepAliveProvider.js
import React, { useReducer, useCallback } from "react";
import CacheContext from "./CacheContext";
import cacheReducer from "./cacheReducer";
import * as cacheTypes from "./cache-types";
function KeepAliveProvider(props) {let [cacheStates, dispatch] = useReducer(cacheReducer, {});const mount = useCallback(({ cacheId, element }) => {// 挂载元素方法,提供子组件调用挂载元素if (cacheStates[cacheId]) {let cacheState = cacheStates[cacheId];if (cacheState.status === cacheTypes.DESTROY) {let doms = cacheState.doms;doms.forEach((dom) => dom.parentNode.removeChild(dom));dispatch({ type: cacheTypes.CREATE, payload: { cacheId, element } }); // 创建缓存}} else {dispatch({ type: cacheTypes.CREATE, payload: { cacheId, element } }); // 创建缓存}},[cacheStates]);let handleScroll = useCallback(// 缓存滚动条(cacheId, { target }) => {if (cacheStates[cacheId]) {let scrolls = cacheStates[cacheId].scrolls;scrolls[target] = target.scrollTop;}},[cacheStates]);return (<CacheContext.Providervalue={{ mount, cacheStates, dispatch, handleScroll }}>{props.children}{/* cacheStates维护所有缓存信息, dispatch派发修改缓存状态*/}{Object.values(cacheStates).filter((cacheState) => cacheState.status !== cacheTypes.DESTROY).map(({ cacheId, element }) => (<divid={`cache_${cacheId}`}key={cacheId}// 原生div中声明ref,当div渲染到页面,会执行ref中的回调函数,这里在id为cache_${cacheId}的div渲染完成后,会继续渲染子元素ref={(dom) => {let cacheState = cacheStates[cacheId];if (dom &&(!cacheState.doms || cacheState.status === cacheTypes.DESTROY)) {let doms = Array.from(dom.childNodes);dispatch({type: cacheTypes.CREATED,payload: { cacheId, doms },});}}}>{element}</div>))}</CacheContext.Provider>);
}
const useCacheContext = () => {const context = React.useContext(CacheContext);if (!context) {throw new Error("useCacheContext必须在Provider中使用");}return context;
};
export { KeepAliveProvider, useCacheContext };
withKeepAlive.js
import React, { useContext, useRef, useEffect } from "react";
import CacheContext from "./CacheContext";
import * as cacheTypes from "./cache-types";
function withKeepAlive(OldComponent,{ cacheId = window.location.pathname, scroll = false }
) {return function (props) {const { mount, cacheStates, dispatch, handleScroll } =useContext(CacheContext);const ref = useRef(null);useEffect(() => {if (scroll) {// scroll = true, 监听缓存组件的滚动事件,调用handleScroll()缓存滚动条ref.current.addEventListener("scroll",handleScroll.bind(null, cacheId),true);}}, [handleScroll]);useEffect(() => {let cacheState = cacheStates[cacheId];if (cacheState &&cacheState.doms &&cacheState.status !== cacheTypes.DESTROY) {// 如果真实dom已经存在,且状态不是DESTROY,则用当前的真实domlet doms = cacheState.doms;doms.forEach((dom) => ref.current.appendChild(dom));if (scroll) {// 如果scroll = true, 则将缓存中的scrollTop拿出来赋值给当前domdoms.forEach((dom) => {if (cacheState.scrolls[dom])dom.scrollTop = cacheState.scrolls[dom];});}} else {// 如果还没产生真实dom,派发生成mount({cacheId,element: <OldComponent {...props} dispatch={dispatch} />,});}}, [cacheStates, dispatch, mount, props]);return <div id={`keepalive_${cacheId}`} ref={ref} />;};
}
export default withKeepAlive;
index.js
export { KeepAliveProvider } from "./KeepAliveProvider";
export {default as withKeepAlive} from './withKeepAlive';
使用:
1.用<KeepAliveProvider></KeepAliveProvider>将目标缓存组件或者父级包裹;
2.将需要缓存的组件,传入withKeepAlive方法中,该方法返回一个缓存组件;
3.使用该组件;
App.js
import React from "react";
import {BrowserRouter,Link,Route,Switch,
} from "react-router-dom";
import Home from "./Home.js";
import List from "./List.js";
import Detail from "./Detail.js";
import { KeepAliveProvider, withKeepAlive } from "./keepalive-cpn";const KeepAliveList = withKeepAlive(List, { cacheId: "list", scroll: true });function App() {return (<KeepAliveProvider><BrowserRouter><ul><li><Link to="/">首页</Link></li><li><Link to="/list">列表页</Link></li><li><Link to="/detail">详情页A</Link></li></ul><Switch><Route path="/" component={Home} exact></Route><Route path="/list" component={KeepAliveList}></Route><Route path="/detail" component={Detail}></Route></Switch></BrowserRouter></KeepAliveProvider>);
}export default App;
效果:
假设有个需求,从首页到列表页,需要清空搜索条件,重新请求数据,即回到首页,需要清除列表页的缓存。
上面的KeepAliveProvider.js中,暴露了一个useCacheContext()的hook,该hook返回了缓存组件相关数据和方法,这里可以用于清除缓存:
Home.js
import React, { useEffect } from "react";
import { DESTROY } from "./keepalive-cpn/cache-types";
import { useCacheContext } from "./keepalive-cpn/KeepAliveProvider";const Home = () => {const { cacheStates, dispatch } = useCacheContext();const clearCache = () => {if (cacheStates && dispatch) {for (let key in cacheStates) {if (key === "list") {dispatch({ type: DESTROY, payload: { cacheId: key } });}}}};useEffect(() => {clearCache();// eslint-disable-next-line}, []);return (<div><div>首页</div></div>);
};export default Home;
效果:
至此,react简易版的keepalive组件已经完成啦~
脚踏实地行,海阔天空飞
React中实现keepalive组件缓存效果相关推荐
- vue中使用keepAlive组件缓存遇到的坑
项目开发中在用户由分类页category进入detail需保存用户状态,查阅了Vue官网后,发现vue2.0提供了一个keep-alive组件. 上一篇讲了keep-alive的基本用法,现在说说遇到 ...
- 如何在React中从其父组件更改子组件的状态
by Johny Thomas 约翰尼·托马斯(Johny Thomas) 如何在React中从其父组件更改子组件的状态 (How to change the state of a child com ...
- [react] 在react中怎样改变组件状态,以及状态改变的过程是什么?
[react] 在react中怎样改变组件状态,以及状态改变的过程是什么? 使用this.setState改变组件的状态 改变的过程中,React Fiber Reconciler遍历了整个Fiber ...
- [react] 描述下在react中无状态组件和有状态组件的区别是什么?
[react] 描述下在react中无状态组件和有状态组件的区别是什么? 1,无状态组件主要用来定义模板,接收来自父组件props传递过来的数据,使用{props.xxx}的表达式把props塞到模板 ...
- [react] 在react中无状态组件有什么运用场景
[react] 在react中无状态组件有什么运用场景 适用于逻辑简单的纯展示的场景,如资料卡片等 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易, 但坚持一定很酷.欢迎大家一起讨论 主 ...
- react中创建一个组件_如何使用React和MomentJS创建一个Countdown组件
react中创建一个组件 Recently I had to create a Countdown for one of my other projects, and I thought that i ...
- React中的纯组件
React中的纯组件 React提供了一种基于浅比较模式来确定是否应该重新渲染组件的类React.PureComponent,通常只需要继承React.PureComponent就可以定义一个纯组件. ...
- react中实现取色器的效果React Color
前言: react中实现取色器的效果 官网地址:入口 源码:https://github.com/casesandberg/react-color.git 实现效果: 步骤: 第一:引入 npm in ...
- react中的keep-alive功能
在vue中可以很方便的实现keep-alive功能,但是在react中却不是很方便,遇事不要慌,度娘来帮忙. react-keep-alive 首先找到的是react-keep-alive,一波操作之 ...
最新文章
- java 分析jstack日志_望闻问切使用jstack和jmap剖析java进程各种疑难杂症
- PHPcms 系统简单使用
- 分布式事务原理及实战seata(转自微信公众号 终码一生 )
- ----uni-app之修改头像----
- boost::mpl模块实现vector_c相关的测试程序
- 主成份(PCA)与奇异值分解(SVD)的通俗解释
- Fancybox丰富的弹出层效果
- Python实战技术 - Python虚拟隔离环境 和 Docker技术
- Dempster证据理论python复现
- 微信外卖点餐系统开发教程
- 远程连接mysql失败异常,未配置权限,skip-name-resolve以及防火墙
- 有源码如何搭建网站(从零开始搭建教程)
- deepin Linux 安装 tar,Linux Deepin 15.11安装更新firefox flash player
- Android 13 Beta 版发布,诸多亮点不容错过
- 保护眼睛颜色的RGB数值
- android计算器退格键,计算器有关问题,如何写退格键
- 如何及时汇报反馈工作
- Java简简单单抢红包小程序(代码)
- Windows下如何启动Redis服务?
- 计算机毕业设计基于Android宠物领养救助系统app——计算机毕业设计