好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

使用 :

  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;

效果 :

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

上面的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;

效果 :

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

 

脚踏实地行,海阔天空飞

 

查看更多关于React中实现keepalive组件缓存效果的详细内容...

  阅读:54次