react缓存页面react-keepalive-router
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react缓存页面react-keepalive-router相关的知识,希望对你有一定的参考价值。
参考技术A https://blog.csdn.net/sinat_17775997/article/details/123211231React中实现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: scrollTop
cacheId: payload.cacheId, // 缓存Id
element: payload.element, // 需要渲染的虚拟DOM
doms: undefined, // 当前的虚拟dom所对应的真实dom
status: 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.Provider
value= mount, cacheStates, dispatch, handleScroll
>
props.children
/* cacheStates维护所有缓存信息, dispatch派发修改缓存状态*/
Object.values(cacheStates)
.filter((cacheState) => cacheState.status !== cacheTypes.DESTROY)
.map(( cacheId, element ) => (
<div
id=`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,则用当前的真实dom
let doms = cacheState.doms;
doms.forEach((dom) => ref.current.appendChild(dom));
if (scroll)
// 如果scroll = true, 则将缓存中的scrollTop拿出来赋值给当前dom
doms.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缓存页面react-keepalive-router的主要内容,如果未能解决你的问题,请参考以下文章
「react缓存页面」从需求到开源(我是怎么样让产品小姐姐刮目相看的)