typeScript+react hooks实现多tab页

1,134 阅读2分钟

实现思路

左侧菜单和tab页公用同一数据,菜单的激活项和tab项由同一变量控制

建立全局变量

通过useContext,useReducer模拟实现redux

import React, { ReactElement, useContext, useReducer } from 'react';

interface Props {
  reducer: any;
  initValue: any;
  children: any;
}

let a: any;

export const AppContext = React.createContext(a);

export function AppProvider({
  reducer,
  initValue,
  children,
}: Props): ReactElement {
  return (
    <AppContext.Provider value={useReducer(reducer, initValue)}>
      {children}
    </AppContext.Provider>
  );
}

export const useAppState = (): any => useContext(AppContext);

注入应用中

const App = (): JSX.Element => {
  return (
    <AppProvider initValue={initState()} reducer={reducer}>
      <BrowserRouter>
        <Switch>
          <Route path="/Manage" component={CoreLayout} />
          <Redirect from="/" to="Manage" />
        </Switch>
      </BrowserRouter>
    </AppProvider>
  );
};

export const useAppState = (): any => useContext(AppContext);

export default App;

左侧菜单

const { Sider } = Layout;
const { SubMenu } = Menu;

function SideMenu(): JSX.Element {
  const [collapsed, setCollapsed] = useState(false);

  const [state, dispatch] = useAppState();
  const { activeTabKey, menuList } = state;

  const handleClickMenuItem = (): void => {
    const tabKey = window.location.pathname;
    dispatch({
      type: MENU_CLICK,
      tabKey,
    });
  };

  const renderMenu = (menus: MenuInterface[]): any => {
    return menus.map((menu: MenuInterface) => {
      if (menu.children && menu.children.length > 0) {
        return (
          <SubMenu
            key={menu.id}
            style={{ display: menu.hidden ? 'none' : 'block' }}
            title={
              <span>
                {menu.icon && <Icon type={menu.icon} />}
                <span>
                  <span className="menu-text">{menu.name}</span>
                </span>
              </span>
            }
          >
            {renderMenu(menu.children)}
          </SubMenu>
        );
      }
      return (
        <Menu.Item
          key={menu.id}
          style={{ display: menu.hidden ? 'none' : 'block' }}
          onClick={handleClickMenuItem}
        >
          <Link
            to={{
              pathname: menu.url,
              state: { action: 'RESET' },
            }}
          >
            {menu.icon && <Icon type={menu.icon} />}
            <span className="menu-text">{menu.name}</span>
          </Link>
        </Menu.Item>
      );
    });
  };

  return (
    <Sider
      collapsible
      collapsedWidth={80}
      breakpoint="sm"
      width={200}
      collapsed={collapsed}
      onCollapse={setCollapsed}
    >
      <Menu
        style={{ overflowY: 'auto' }}
        mode="inline"
        theme="dark"
        inlineIndent={20}
        selectedKeys={[activeTabKey]}
      >
        {renderMenu(menuList)}
      </Menu>
    </Sider>
  );
}

export default SideMenu;

Tab页实现

const { TabPane } = Tabs;

export default function MainLayout(): ReactElement {
  const [state, dispatch] = useAppState();

  const { tabList, activeTabKey } = state;

  const handleTabChange = (activeKey: string): void => {
    dispatch({
      type: CHANGE_TAB,
      tabKey: activeKey,
    });
  };

  const remove = (targetKey: string): void => {
    dispatch({
      type: DELETE_TAB,
      tabKey: targetKey,
    });
  };

  const hanleEdit = (
    targetKey: string | React.MouseEvent<HTMLElement>,
    action: string
  ): void => {
    if (action === 'remove') {
      remove(String(targetKey));
    }
  };

  return (
    <div>
      <Tabs
        hideAdd
        onChange={handleTabChange}
        activeKey={activeTabKey}
        type="editable-card"
        onEdit={hanleEdit}
      >
        {tabList.map((panel: MenuInterface) => (
          <TabPane tab={panel.name} key={panel.id}>
            {getTabscomponent(panel.id).component}
          </TabPane>
        ))}
      </Tabs>
    </div>
  );
}

多Tab页实现

const Loading = (): JSX.Element => <span>Loading...</span>;

const Production = Loadable({
  loader: () => import('../routers/Manage/Producation/Production/Production'),
  loading: Loading,
  delay: 150,
});

const NotFound = Loadable({
  loader: () => import('../routers/404/404'),
  loading: Loading,
  delay: 150,
});

export interface TabModal {
  name: string;
  id?: string;
  component: JSX.Element;
}

export const getTabscomponent = (key: string): TabModal => {
  let newKey = key;
  if (key.includes('?')) {
    newKey = key.split('?')[0];
  }

  const tab: TabModal = {
    name: '没有找到',
    component: <NotFound />,
  };

  switch (newKey) {
    case '/Manage/Production':
      tab.name = '页面A';
      tab.component = <Production />;
      break;
    default:
      break;
  }
  return tab;
};

总结

左侧菜单的Link的path和tab页tabKey都是同一路径