在浏览器中使用React-router 4,如何制作多级标签菜单?

时间:2018-02-14 17:45:28

标签: tabs react-router react-router-v4 react-router-dom multi-level

我在浏览器应用中使用react-router-domreact-router-config v4。我需要从路线图中获得多级选项卡菜单(每条路线要么有一个要渲染的组件,要么子路径要显示为下一级菜单)。

我想获得以下菜单结构:

multilevel menu

从这样的解释:

 const routes = [
  {
    path: "/Tab1",
    name: "Tab 01"
    component: Tab1
  },
  {
    path: "/Tab2",
    name: "Tab 02"
    component: Tab12
  },
  {
    path: "/Tab3",
    name: "Tab 03"
    component: null,
    routes: [
      {
        path: "/Tab3/SubTab1",
        name: "SubTab 01"
        component: SubTab1
      },
      {
        path: "/Tab3/SubTab2",
        name: "SubTab 02"
        component: SubTab2
      },
      {
        path: "/Tab3/SubTab3",
        name: "SubTab 03"
        component: null,
        routes: [
         ...
        ]
      },
    ]
  },
  ...
];

1 个答案:

答案 0 :(得分:2)

我提出了一个令人满意的解决方案(在TypeScript片段之后很长,但大多数是路由图定义)。

import * as React from "react";
import * as ReactDOM from "react-dom";
import { BrowserRouter, Route, Link, } from "react-router-dom";
import { renderRoutes, RouteConfig, MatchedRoute } from 'react-router-config'
import { Location } from "history";

declare module 'react-router-config'{
    interface RouteConfig{
        tabName?: string;
        defaultSubpath?: string;
    }

    interface MatchedRoute<T>{
        location: Location;
    }
}

// todo optimize with memoization?
function getActiveRoutes(match: MatchedRoute<any>):RouteConfig[]{

  const currentPath = match.location.pathname;
  const routes = match.route.routes;

  let activeRoutes:RouteConfig[] = [];

  fillActiveRoutes(routes);

  return activeRoutes;

  function fillActiveRoutes(current: RouteConfig[]){
    for(const route of current){

      activeRoutes.push(route);
      let isActive = false;

      if(!route.routes || route.routes.length === 0){
        isActive = route.path === currentPath;
      } else if(route.routes) {
        let isActive = fillActiveRoutes(route.routes);
      }

      if(isActive === false){
        activeRoutes.pop();
      } else {
        break;
      }
    }
  }
}

const ChildLinks = (match: MatchedRoute<any>) => {
  let activeRoutes = getActiveRoutes(match);

  return(<div>
            {match.route.routes.map((route) => {
                  let isActive = activeRoutes.some(x => x === route);
                  let to = route.defaultSubpath || route.path;
                  let key = 'main-tabs-link-' + route.path;
                  let label = isActive ? `  [${route.tabName}]  ` : `  ${route.tabName}  `;
                  return (<Link to={to} key={key}> {label} </Link>);
                })
            }
        </div>);
}

const EmptyRenderer:React.StatelessComponent<MatchedRoute<any>> = (match: MatchedRoute<any>) => (<div>
  {ChildLinks(match)}
  {renderRoutes(match.route.routes)}
</div>);

const Root:React.StatelessComponent<MatchedRoute<any>> = (match: MatchedRoute<any>) =>  (<div>
    <h1>Root</h1>
    { EmptyRenderer(match) }
  </div>);

const StaticDiv: (content:string) => React.StatelessComponent<MatchedRoute<any>> = (content:string) => 
        () => (<div>{content}</div>)

const routes:RouteConfig[]  = [
  { component: Root,
    routes: [
      { path: '/A/',
        tabName: 'A',
        exact: true,
        component: StaticDiv("A")
      },
      { path: '/B/',
        tabName: 'B',
        defaultSubpath: '/B/2/',
        exact: false,
        component: EmptyRenderer,
        routes: [
            { 
            path: '/B/1/',
            exact: true,
            tabName: "B1",
            component: StaticDiv("B1")
            },{ 
            path: '/B/2/',
            exact: true,
            tabName: "B2",
            component: StaticDiv("B2")
          },{ 
            path: '/B/3/',
            exact: true,
            tabName: "B3",
            component: StaticDiv("B3")
        }]
      },
      { path: '/C/',
        tabName: 'C',
        defaultSubpath: '/C/3/Z/',
        exact: false,
        component: EmptyRenderer,
        routes: [
            { 
            path: '/C/1/',
            exact: true,
            tabName: "C1",
            component: StaticDiv("C1")
            },{ 
            path: '/C/2/',
            exact: true,
            tabName: "C2",
            component: StaticDiv("C2")
          },{ 
            path: '/C/3/',
            defaultSubpath: '/C/3/Z/',
            exact: false,
            tabName: "C3",
            component:EmptyRenderer,
            routes: [
                { 
                path: '/C/3/X/',
                exact: true,
                tabName: "C3X",
                component: StaticDiv("C3X")
                },{ 
                path: '/C/3/Y/',
                exact: true,
                tabName: "C3Y",
                component: StaticDiv("C3Y")
              },{ 
                path: '/C/3/Z/',
                exact: true,
                tabName: "C3Z",
                component: StaticDiv("C3Z")
            }]
        }]
      }
    ]
  }
]

export const Example = () => (<BrowserRouter>
    {renderRoutes(routes)}
  </BrowserRouter>);

使用的Lib版本:

"dependencies": {
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-router": "^4.2.0",
    "react-router-config": "^1.0.0-beta.4",
    "react-router-dom": "^4.2.2"
  },
  "devDependencies": {
    "@types/react": "^16.0.38",
    "@types/react-dom": "^16.0.4",
    "@types/react-router-config": "^1.0.6",
    "typescript": "^2.7.1"
  }