React中實(shí)現(xiàn)keepalive組件緩存效果的方法詳解
背景
由于react官方并沒(méi)有提供緩存組件相關(guān)的api(類似vue中的keepalive),在某些場(chǎng)景,會(huì)使得頁(yè)面交互性變的很差,比如在有搜索條件的表格頁(yè)面,點(diǎn)擊某一條數(shù)據(jù)跳轉(zhuǎn)到詳情頁(yè)面,再返回表格頁(yè)面,會(huì)重新請(qǐng)求數(shù)據(jù),搜索條件也將清空,用戶得重新輸入搜索條件,再次請(qǐng)求數(shù)據(jù),大大降低辦公效率,如圖:

目標(biāo):封裝keepalive緩存組件,實(shí)現(xiàn)組件的緩存,并暴露相關(guān)方法,可以手動(dòng)清除緩存。
版本:React 17,react-router-dom 5
結(jié)構(gòu)

代碼
cache-types.js
// 緩存狀態(tài) export const CREATE = 'CREATE'; // 創(chuàng)建 export const CREATED = 'CREATED'; // 創(chuàng)建成功 export const ACTIVE = 'ACTIVE'; // 激活 export const DESTROY = 'DESTROY'; // 銷毀
CacheContext.js
import React from 'react'; const CacheContext = React.createContext(); export default CacheContext;
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 }) => {
// 掛載元素方法,提供子組件調(diào)用掛載元素
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 } }); // 創(chuàng)建緩存
}
} else {
dispatch({ type: cacheTypes.CREATE, payload: { cacheId, element } }); // 創(chuàng)建緩存
}
},
[cacheStates]
);
let handleScroll = useCallback(
// 緩存滾動(dòng)條
(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維護(hù)所有緩存信息, dispatch派發(fā)修改緩存狀態(tài)*/}
{Object.values(cacheStates)
.filter((cacheState) => cacheState.status !== cacheTypes.DESTROY)
.map(({ cacheId, element }) => (
<div
id={`cache_${cacheId}`}
key={cacheId}
// 原生div中聲明ref,當(dāng)div渲染到頁(yè)面,會(huì)執(zhí)行ref中的回調(diào)函數(shù),這里在id為cache_${cacheId}的div渲染完成后,會(huì)繼續(xù)渲染子元素
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, 監(jiān)聽(tīng)緩存組件的滾動(dòng)事件,調(diào)用handleScroll()緩存滾動(dòng)條
ref.current.addEventListener(
"scroll",
handleScroll.bind(null, cacheId),
true
);
}
}, [handleScroll]);
useEffect(() => {
let cacheState = cacheStates[cacheId];
if (
cacheState &&
cacheState.doms &&
cacheState.status !== cacheTypes.DESTROY
) {
// 如果真實(shí)dom已經(jīng)存在,且狀態(tài)不是DESTROY,則用當(dāng)前的真實(shí)dom
let doms = cacheState.doms;
doms.forEach((dom) => ref.current.appendChild(dom));
if (scroll) {
// 如果scroll = true, 則將緩存中的scrollTop拿出來(lái)賦值給當(dāng)前dom
doms.forEach((dom) => {
if (cacheState.scrolls[dom])
dom.scrollTop = cacheState.scrolls[dom];
});
}
} else {
// 如果還沒(méi)產(chǎn)生真實(shí)dom,派發(fā)生成
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>將目標(biāo)緩存組件或者父級(jí)包裹;
2.將需要緩存的組件,傳入withKeepAlive方法中,該方法返回一個(gè)緩存組件;
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="/">首頁(yè)</Link>
</li>
<li>
<Link to="/list">列表頁(yè)</Link>
</li>
<li>
<Link to="/detail">詳情頁(yè)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;效果:

假設(shè)有個(gè)需求,從首頁(yè)到列表頁(yè),需要清空搜索條件,重新請(qǐng)求數(shù)據(jù),即回到首頁(yè),需要清除列表頁(yè)的緩存。
上面的KeepAliveProvider.js中,暴露了一個(gè)useCacheContext()的hook,該hook返回了緩存組件相關(guān)數(shù)據(jù)和方法,這里可以用于清除緩存:
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>首頁(yè)</div>
</div>
);
};
export default Home;效果:

至此,react簡(jiǎn)易版的keepalive組件已經(jīng)完成啦~
到此這篇關(guān)于React中實(shí)現(xiàn)keepalive組件緩存效果的方法詳解的文章就介紹到這了,更多相關(guān)React keepalive組件緩存效果內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react 不用插件實(shí)現(xiàn)數(shù)字滾動(dòng)的效果示例
這篇文章主要介紹了react 不用插件實(shí)現(xiàn)數(shù)字滾動(dòng)的效果示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
react如何實(shí)現(xiàn)側(cè)邊欄聯(lián)動(dòng)頭部導(dǎo)航欄效果
這篇文章主要介紹了react如何實(shí)現(xiàn)側(cè)邊欄聯(lián)動(dòng)頭部導(dǎo)航欄效果,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
React hook 'useState' is calle
這篇文章主要為大家介紹了React hook 'useState' is called conditionally報(bào)錯(cuò)解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
詳解react-navigation6.x路由庫(kù)的基本使用
最近兩個(gè)項(xiàng)目都用到了React Navigation,所以就研究一下如何使用,本文主要介紹了react-navigation6.x路由庫(kù)的基本使用,感興趣的可以了解一下2021-11-11
詳解react中useCallback內(nèi)部是如何實(shí)現(xiàn)的
前幾天有人在問(wèn)在useCallback函數(shù)如果第二個(gè)參數(shù)為空數(shù)組, 為什么拿不到最新的state值,那么這一章就來(lái)分析一下useCallback內(nèi)部是如何實(shí)現(xiàn)的,感興趣的小伙伴跟著小編一起來(lái)學(xué)習(xí)吧2023-07-07
react?事項(xiàng)懶加載的三種方法及使用場(chǎng)景
這篇文章主要介紹了react?事項(xiàng)懶加載的三種方法及使用場(chǎng)景,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
react-native android狀態(tài)欄的實(shí)現(xiàn)
這篇文章主要介紹了react-native android狀態(tài)欄的實(shí)現(xiàn),使?fàn)顟B(tài)欄顏色與App顏色一致,使用戶界面更加整體。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06

