背景:由于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组件缓存效果相关推荐

  1. vue中使用keepAlive组件缓存遇到的坑

    项目开发中在用户由分类页category进入detail需保存用户状态,查阅了Vue官网后,发现vue2.0提供了一个keep-alive组件. 上一篇讲了keep-alive的基本用法,现在说说遇到 ...

  2. 如何在React中从其父组件更改子组件的状态

    by Johny Thomas 约翰尼·托马斯(Johny Thomas) 如何在React中从其父组件更改子组件的状态 (How to change the state of a child com ...

  3. [react] 在react中怎样改变组件状态,以及状态改变的过程是什么?

    [react] 在react中怎样改变组件状态,以及状态改变的过程是什么? 使用this.setState改变组件的状态 改变的过程中,React Fiber Reconciler遍历了整个Fiber ...

  4. [react] 描述下在react中无状态组件和有状态组件的区别是什么?

    [react] 描述下在react中无状态组件和有状态组件的区别是什么? 1,无状态组件主要用来定义模板,接收来自父组件props传递过来的数据,使用{props.xxx}的表达式把props塞到模板 ...

  5. [react] 在react中无状态组件有什么运用场景

    [react] 在react中无状态组件有什么运用场景 适用于逻辑简单的纯展示的场景,如资料卡片等 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易, 但坚持一定很酷.欢迎大家一起讨论 主 ...

  6. react中创建一个组件_如何使用React和MomentJS创建一个Countdown组件

    react中创建一个组件 Recently I had to create a Countdown for one of my other projects, and I thought that i ...

  7. React中的纯组件

    React中的纯组件 React提供了一种基于浅比较模式来确定是否应该重新渲染组件的类React.PureComponent,通常只需要继承React.PureComponent就可以定义一个纯组件. ...

  8. react中实现取色器的效果React Color

    前言: react中实现取色器的效果 官网地址:入口 源码:https://github.com/casesandberg/react-color.git 实现效果: 步骤: 第一:引入 npm in ...

  9. react中的keep-alive功能

    在vue中可以很方便的实现keep-alive功能,但是在react中却不是很方便,遇事不要慌,度娘来帮忙. react-keep-alive 首先找到的是react-keep-alive,一波操作之 ...

最新文章

  1. java 分析jstack日志_望闻问切使用jstack和jmap剖析java进程各种疑难杂症
  2. PHPcms 系统简单使用
  3. 分布式事务原理及实战seata(转自微信公众号 终码一生 )
  4. ----uni-app之修改头像----
  5. boost::mpl模块实现vector_c相关的测试程序
  6. 主成份(PCA)与奇异值分解(SVD)的通俗解释
  7. Fancybox丰富的弹出层效果
  8. Python实战技术 - Python虚拟隔离环境 和 Docker技术
  9. Dempster证据理论python复现
  10. 微信外卖点餐系统开发教程
  11. 远程连接mysql失败异常,未配置权限,skip-name-resolve以及防火墙
  12. 有源码如何搭建网站(从零开始搭建教程)
  13. deepin Linux 安装 tar,Linux Deepin 15.11安装更新firefox flash player
  14. Android 13 Beta 版发布,诸多亮点不容错过
  15. 保护眼睛颜色的RGB数值
  16. android计算器退格键,计算器有关问题,如何写退格键
  17. 如何及时汇报反馈工作
  18. Java简简单单抢红包小程序(代码)
  19. Windows下如何启动Redis服务?
  20. 计算机毕业设计基于Android宠物领养救助系统app——计算机毕业设计

热门文章

  1. 软件复用的优点,举例描述支持软件复用的方法和内容。
  2. 免费实用的jpg转换成pdf工具
  3. uni-app swiper设置自定义高度
  4. CAD快速修改角度小技巧
  5. Maven | filtering,filter和profile标签使用
  6. 大学计算机基础第三版重要知识点,大一大学计算机基础课程知识点
  7. 运营——线上引流9大方法
  8. 今天安装了VS2008中文版SP1。
  9. 嗨,我亲爱的朋友们!心存感恩1
  10. 从零开始Android游戏编程(第二版) 第一版前言