React中实现keepalive组件缓存效果

科技资讯 投稿 7100 0 评论

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';

使用

2.将需要缓存的组件,传入withKeepAlive方法中,该方法返回一个缓存组件;

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返回了缓存组件相关数据和方法,这里可以用于清除缓存:

 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组件缓存效果

赞同 (45) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽