programing

브라우저의 4번 리액션루터, 멀티레벨 탭 메뉴 만드는 방법?

prostudy 2022. 3. 17. 00:49
반응형

브라우저의 4번 리액션루터, 멀티레벨 탭 메뉴 만드는 방법?

브라우저 앱에서 react-router-domreact-router-config v4를 사용하고 있다.경로 그래프(각 경로에는 렌더링할 구성 요소가 있거나 다음 레벨 메뉴로 표시할 하위 경로가 있음)에서 다중 탭 메뉴가 필요하다.

다음과 같은 메뉴 구조를 받고 싶다.

다단계 메뉴

다음과 같은 설명에서:

 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: [
         ...
        ]
      },
    ]
  },
  ...
];

나는 만족스러운 해결책을 생각해냈다(타입스크립트 코드 조각에 따르는 것은 길지만, 대부분은 경로 그래프 정의다).

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"
  }

참조URL: https://stackoverflow.com/questions/48793284/react-router-4-in-browser-how-to-make-multilevel-tab-menu

반응형