5

React中实现keepalive组件缓存效果 - coder__wang

 1 year ago
source link: https://www.cnblogs.com/coder--wang/p/17049408.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

React中实现keepalive组件缓存效果 - coder__wang - 博客园

文*弱*书*生

脚踏实地行,海阔天空飞

背景:由于react官方并没有提供缓存组件相关的api(类似vue中的keepalive),在某些场景,会使得页面交互性变的很差,比如在有搜索条件的表格页面,点击某一条数据跳转到详情页面,再返回表格页面,会重新请求数据,搜索条件也将清空,用户得重新输入搜索条件,再次请求数据,大大降低办公效率,如图:

1913379-20230113151232784-1449025195.gif

目标:封装keepalive缓存组件,实现组件的缓存,并暴露相关方法,可以手动清除缓存。

版本:React 17,react-router-dom 5

结构

1913379-20230113134348706-1227396899.png

代码:

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

使用

  1.用<KeepAliveProvider></KeepAliveProvider>将目标缓存组件或者父级包裹;

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

  3.使用该组件;

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;

效果

1913379-20230113152010570-1992216955.gif

假设有个需求,从首页到列表页,需要清空搜索条件,重新请求数据,即回到首页,需要清除列表页的缓存。

上面的KeepAliveProvider.js中,暴露了一个useCacheContext()的hook,该hook返回了缓存组件相关数据和方法,这里可以用于清除缓存:

Home.js

 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;

效果

1913379-20230113162638685-206349254.gif

至此,react简易版的keepalive组件已经完成啦~

脚踏实地行,海阔天空飞


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK