2

Using tabs navigation in background projects is an important function

Let’s introduce how to implement this function with the umi framework and the corresponding plug-in.

Reference: react-activation

Reference implementation: implements a

The current architecture design used in the project is umi3+react17+antd pro5

1. Introduce and use related plug-ins

Plug-in address: umi-plugin-keep-alive

npm install umi-plugin-keep-alive --save
# or
yarn add umi-plugin-keep-alive

2. Public component packaging

Create KeepAvlieTabs under the components folder
Please modify your own requirements for the corresponding less style

For related keepAlive and life cycle usage plans and questions, please go to the above link react-activation to view

Create index.tsx file

// /components/KeepAvlieTabs/index.tsx
import { useAliveController } from 'react-activation';
import { arrayDeduplicate } from '@/utils/Array';
import type { CachingNode } from './type';
import Tab from './Tab';
import styles from './index.less';
import { useHistory, useLocation } from 'umi';

export default function KeepAliveTabs() {
  // history导航
  const history = useHistory();
  // 本地路由信息
  const location = useLocation();
  // 获取缓存节点方法和信息
  const { getCachingNodes } = useAliveController();
  const cachingNodes: CachingNode[] = getCachingNodes();
  // 因为是异步组件,需要在这儿处理一下缓存中的重复问题
  let nodes: CachingNode[] = arrayDeduplicate(cachingNodes, 'path');
  // 首页不参与tabs切换的关闭操作
  nodes = nodes.filter((item) => item.path !== '/home');
  return (
    <ul className={styles['alive-tabs']}>
      <li
        className={location.pathname === '/home' ? styles.home_active : styles.home_deactive}
        onClick={() => {
          history.push('/home');
        }}
      >
        <div className="tags-nav">
          <span>首页</span>
        </div>
      </li>
      {nodes.map((node) => (
        <Tab key={node!.id} node={node} />
      ))}
    </ul>
  );
}

Create Tab.tsx file

// /components/KeepAvlieTabs/Tab.tsx
import { useHistory, useLocation } from 'umi';
import { useAliveController } from 'react-activation';
import { CloseCircleOutlined } from '@ant-design/icons';
import type { CachingNode } from './type';

import styles from './index.less';

export default function Tab({ node }: { node: CachingNode }) {
  const history = useHistory();
  const location = useLocation();
  // 同上,dropScope是释放节点,点删除后删掉当前节点信息
  const { getCachingNodes, dropScope } = useAliveController();
  const cachingNodes: CachingNode[] | any[] = getCachingNodes();
  // 执行tab的删除操作
  function dropTab(e: React.MouseEvent<HTMLButtonElement>) {
    e.stopPropagation();
    // 如果关闭激活中的 KeepAlive Tab,需要先离开当前路由
    // 触发 KeepAlive unactivated 后再进行 drop
    if (location.pathname === node.path) {
      // 路由异步加载控制
      const unlisten = history.listen(() => {
        setTimeout(() => {
          dropScope(node.name as string | RegExp);
        }, 30);
      });
      unlisten();
      // 前往排除当前 node 后的最后一个 tab
      if (cachingNodes.length <= 1) {
        history.push('/');
      } else {
        const { path } = cachingNodes.filter((item) => item.path !== node.path).pop();
        history.push(path);
      }
    } else {
      dropScope(node.name as string | RegExp);
    }
  }
  // 设置当前tab的样式
  const className = () => {
    if (location.pathname === node.path) {
      if (location.pathname === '/home') {
        return `${styles.active}  ${styles.home_active}`;
      }
      return `${styles.active}`;
    }
    return `${styles.deactive}`;
  };
  
  return (
    <li
      className={className()}
      onClick={() => {
        history.push(node.path);
      }}
    >
      <div className="tags-nav">
        <span>{node.name}</span>
        {<CloseCircleOutlined className={styles['close-btn']} onClick={dropTab} />}
      </div>
    </li>
  );
}

Create type file type.ts

export type CachingNode = {
  createTime: number;
  updateTime: number;
  name?: string;
  id: string;
  [key: string]: any;
};

Create a style less file

.alive-tabs {
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  background: #fff;

  li {
    position: relative;
    display: inline-block;
    padding: 0 28px 0 10px;
    line-height: 32px;
    vertical-align: top;
    list-style: none;
    border-right: solid 1px #e6e6e6;
    cursor: pointer;
    transition: background 0.2s;
  }

  .active {
    height: 32px;
    background: rgba(#1890ff, 0.1);
    border-bottom: 2px solid #1890ff;
    // background-color: #42b983;
  }

  .home_active {
    height: 32px;
    padding: 0 10px;
    background: rgba(#1890ff, 0.1);
    border-bottom: 2px solid #1890ff;
    // background-color: #42b983;
  }

  .home_deactive {
    padding: 0 10px;
  }

  .deactive {
    background: #fff;
  }

  .close-btn {
    position: absolute;
    top: 11px;
    right: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    line-height: 0;
    outline: none;
    // transform: translateY(-50%);
  }
}

3. Use keepAlive in the component

Create Books/index.tsx

import React from 'react';
import { KeepAlive, useActivate, useUnactivate } from 'react-activation';

const Books: React.FC = () => {
  useActivate(()=>{
    console.log("我被激活了");
  })
  useUnactivate(()=>{
    console.log("我被缓存了");
  })
  return <div>我是被缓存的组件</div>;
};

export default (props: any) => {
  // 下面使用这样的方式去控制路由操作
  // 是为了同步显示路由定义中的title和匹配路由path做对应的操作
  // 可根据自行需求修改当前的逻辑
  return (
    <KeepAlive name={props.route.name} path={props.route.path} saveScrollPosition="screen">
      <Books />
    </KeepAlive>
  );
};

4.keepAliveTabs mounted to the interface

Our page layout has left and right structure, in app.tsx, mounted in our head, beyond the display scroll bar, please add specific styles and functions by yourself
The right mouse button menu is not implemented, close all, switch left and right, please add by yourself, antd has related components

export const layout: RunTimeLayoutConfig = ({ initialState }: any) => {
  return {
    // 控制组件是否可缓存
    disableMobile: true,
    headerContentRender: () => <KeepAliveTabs />,
    ...
  };
};

万年打野易大师
1.5k 声望1.1k 粉丝