【umi-keep-alive bysking】为你的umi 项目引入keepalive 页面缓存

1,100 阅读2分钟

一、常见方案

  • 安装依赖
 yarn add umi-plugin-keep-alive@^0.0.1-beta.35
  • umi配置文件增加插件注入
// .umirc.ts
   {
       plugins: ['umi-plugin-keep-alive'],
   }
  • 项目入口文件App.tsx增加如下代码,修复keepalive组件则umi中无法与useAccess共存的问题
// App.tsx
import { autoFixContext } from 'react-activation';
import jsxDevRuntime from 'react/jsx-dev-runtime';
import jsxRuntime from 'react/jsx-runtime';

autoFixContext(
  [jsxRuntime, 'jsx', 'jsxs', 'jsxDEV'],
  [jsxDevRuntime, 'jsx', 'jsxs', 'jsxDEV'],
);
  • 定义一个高阶函数:when函数最好由外部传递,除非全局统一的逻辑可以写在这里
import { KeepAlive, history } from 'umi';

const KeepAliveWrap = (WrapperCpn: React.FC, config: any = {}) => {
  return (props: any) => {
    const { location } = history;
    return (
      <KeepAlive
        {...config}
        id={location.pathname}
        when={() => {
          console.log(history);

          /** 如果跳转的页面是带import字符串的则缓存路由 */
          if (history.location.pathname.includes('import')) {
            return true;
          }

          // 否则不缓存
          return false;
        }}
      >
        <WrapperCpn {...props} />
      </KeepAlive>
    );
  };
};

export default KeepAliveWrap;
  • 使用的时候,直接包裹一下需要导出的tsx页面组件即可
const PlanHospitalDetail: React.FC = () => {
    return <div></div>
}

export default KeepAliveWrap(PlanHospitalDetail);

二、hack方案:基于路由监听,将数据保存到window,页面渲染的时候再取出来重新赋值


// 实现一个useRouterCache.tsx的hook
import { history, useLocation } from '@umijs/max';
import { useEffect } from 'react';

type typePageStore = {
  [key: string]: any;
};

type typeOnFn = (location: Location, action: string) => void;
type typeListenerFn = {
  getData: () => typePageStore;
  setData: (data: Record<string, any>) => void;
  pageRouteStore: typePageStore;
  location: Location;
  action: string;
};

type typeRouterCacheoptions = {
  pathObj: {
    onPop: (params: typeListenerFn) => void;
    onPush: (params: typeListenerFn) => void;
  };
  /** 路由白名单 */
  pathIncludes: string[];
};
export const useRouterCache = (options: typeRouterCacheoptions) => {
  const location = useLocation();
  const pathName = location.pathname;
  const { pathObj, pathIncludes } = options;

  const initFn = (pathObj) => {
    let popFnList: typeOnFn[] = [];
    let pushFnList: typeOnFn[] = [];
    let { onPop, onPush } = pathObj;
    let pageRouteStore = window.pageRouteStore;
    let getData = () => {
      return window.pageRouteStore?.[pathName];
    };
    let setData = (data = {}) => {
      let originObj = window.pageRouteStore[pathName];
      window.pageRouteStore[pathName] = {
        ...originObj,
        ...data,
      };
    };

    pushFnList.push((location, action) => {
      onPush({
        getData,
        setData,
        pageRouteStore,
        location,
        action,
      });
    });

    popFnList.push((location, action) => {
      onPop({
        getData,
        setData,
        pageRouteStore,
        location,
        action,
      });
    });

    return {
      pushFnList,
      popFnList,
    };
  };

  const clearFn = () => {
    if (!window.pageRouteStore[pathName]) {
      return;
    }
    window.pageRouteStore[pathName].myunlistener?.();
    window.pageRouteStore[pathName].myunlistener = null;
    window.pageRouteStore[pathName] = null;
  };

  useEffect(() => {
    if (!window.pageRouteStore) {
      window.pageRouteStore = {};
    }

    if (!window.pageRouteStore?.[pathName]) {
      window.pageRouteStore[pathName] = {};
    }
    window.pageRouteStore[pathName].myunlistener?.();
    window.pageRouteStore[pathName].myunlistener = history.listen(
      ({ location, action }) => {
        let hasInclude = pathIncludes.some((item) =>
          location.pathname.includes(item),
        );
        // console.log('路径销毁判断', location.pathname, pathIncludes);
        if (!hasInclude && action === 'PUSH') {
          clearFn();
          return;
        }

        const { pushFnList, popFnList } = initFn(pathObj);

        if (action === 'POP') {
          popFnList.forEach((fn) => {
            // @ts-ignore
            fn(location, action);
          });
        } else if (action === 'PUSH') {
          pushFnList.forEach((fn) => {
            // @ts-ignore
            fn(location, action);
          });
        }
      },
    );
  }, []);

  return {
    clearFn,
  };
};

  • 业务方使用
import { useRouterCache } from '@/hooks/useRouterCache';
const refTabData: any = {
  current: null,
};

export const Cmp = () => {

  useRouterCache({
    pathObj: {
      onPop({ getData }) {
          
        // 页面进入的时候会取出数据设置到临时数据保存变量上
        refTabData.current = getData()?.tab;
      },
      onPush({ setData }) {
          
          // 页面离开的时候,会将变更的数据做一个保存
        setData({
          tab: refTabData.current,
        });
      },
    },
    // 在导入和详情这些页面保持监听器的激活,否则会销毁监听器,pathName是当前页面路由,import是导入的页面路由
    pathIncludes: [pathName, 'import'],
  });
  
  useEffect(() => {
  
    // 页面销毁清空临时数据
    return () => {
      refTabData.current = null;
    };
  }, []);
  
  let defaultData = refTabData.current; // 获取记录的默认数据
  
  const onChange = () => {
      // 记录更新后的数据
      refTabData.current = { name: 123 }
  }

}

参考文档: