1. 程式人生 > >003-ant design pro 路由和菜單

003-ant design pro 路由和菜單

部分 doc angle line board 面包屑 腳手架 封裝 ebp

一、概述

  參看地址:https://pro.ant.design/docs/router-and-nav-cn

二、原文摘要

  路由和菜單是組織起一個應用的關鍵骨架,我們的腳手架提供了一些基本的工具及模板,幫助你更方便的搭建自己的路由/菜單。

  如果你想了解更多關於 browserHistoryhashHistory,請參看 構建和發布。

註意:我們的腳手架依賴 dva@2,路由方面是基於 react-router@4 的實現,在寫法以及 API 上與之前的版本有較大不同,所以,在開始前你需要具備一些相關的基礎知識。這裏給出幾篇關鍵文檔:

2.1、基本結構

腳手架通過結合一些配置文件、基本算法及工具函數,搭建好了路由和菜單的基本框架,主要涉及以下幾個模塊/功能:

  • 路由生成 結合路由信息的配置文件和預設的基本算法,提供了在不同層級文件中自動生成路由列表的能力。

  • 菜單生成 由確定數據結構的菜單配置文件生成,其中的菜單項名稱,嵌套路徑與路由有一定關聯關系。

  • 面包屑 組件 PageHeader 中內置的面包屑也可由腳手架提供的配置信息自動生成。

簡單介紹下各個模塊的基本思路。

2.1.1、路由

目前在腳手架中,除了頂層路由,其余路由列表都是自動生成,其中最關鍵的就是中心化配置文件 src/common/router.js,它的主要作用有兩個:

  • 配置路由相關信息。如果只考慮生成路由,你只需要指定每條配置的路徑及對應渲染組件。

  • 輸出路由數據,並將路由數據(routerData)掛載到每條路由對應的組件上。

這樣我們得到一個基本的路由信息對象,它的結構大致是這樣:

{
  ‘/dashboard/analysis‘: {
    component: DynamicComponent(),
    name: ‘分析頁‘,
  },
  
‘/dashboard/monitor‘: { component: DynamicComponent(), name: ‘監控頁‘, }, ‘/dashboard/workplace‘: { component: DynamicComponent(), name: ‘工作臺‘, }, }

為了幫助自動生成路由,在 src/utils/utils.js 中提供了工具函數 getRoutes,它接收兩個參數:當前路由的 match 路徑及路由信息 routerData,主要完成兩個工作:

  • 篩選路由信息,篩選的算法為只保留當前 match.path 下最鄰近的路由層級(更深入的層級留到嵌套路由中自行渲染),舉個例子(每條為一個 route path):

// 當前 match.path 為 /
/a                 // 沒有更近的層級,保留
/a/b               // 存在更近層級 /a,去掉
/c/d               // 沒有更近的層級,保留
/c/e               // 沒有更近的層級,保留
/c/e/f             // 存在更近層級 /c/e,去掉
  • 自動分析路由 exact 參數,除了下面還有嵌套路由的路徑,其余路徑默認設為 exact。

經過 getRoutes 處理之後的路由數據就可直接用於生成路由列表:

// src/layouts/BasicLayout.js
getRoutes(match.path, routerData).map(item => (
  <Route
    key={item.key}
    path={item.path}
    component={item.component}
    exact={item.exact}
  />
))

註意:如果你不需要自動生成路由,也可以用 routerData 自行處理。

2.1.2、菜單

菜單信息配置在 src/common/menu.js 中,它的作用是:

  • 配置菜單相關數據,菜單項的跳轉鏈接為配置項及其所有父級配置 path 參數的拼接。

  • src/common/router.js 提供路由名稱(name)等數據,根據拼接好的跳轉鏈接來匹配相關路由。

如果你的項目並不需要菜單,你也可以直接在 src/common/router.js 中配置 name 信息。

配置文件輸出的菜單數據,可以直接提供給側邊欄組件使用。SiderMenu.js。除了生成菜單,菜單數據還可輔助生成重定向路由等模塊,參考 BasicLayout.js#L154。

2.1.3、面包屑

  之前提到的路由信息 routerData 可以直接傳遞給 PageHeader 組件用以生成面包屑,你可以用 props 或者 context 的方式進行傳遞。腳手架裏的示例。

2.2、需求示例

2.2.1、新增頁面

  腳手架默認提供了兩種布局模板:基礎布局 - BasicLayout 以及 賬戶相關布局 - UserLayout,如上文提供的兩種圖片。

  如果你的頁面可以利用這兩種布局,那麽只需要在路由及菜單配置中增加一條即可:

1》增加路由配置

// src/common/router.js
‘/dashboard/test‘: {
  component: dynamicWrapper(app, [‘monitor‘], () => import(‘../routes/Dashboard/Test‘)),
},

2》菜單配置

// src/common/menu.js
const menuData = [{
  name: ‘dashboard‘,
  icon: ‘dashboard‘,  // https://demo.com/icon.png or <Icon type="dashboard" />
  path: ‘dashboard‘,
  children: [{
    name: ‘分析頁‘,
    path: ‘analysis‘,
  }, {
    name: ‘監控頁‘,
    path: ‘monitor‘,
  }, {
    name: ‘工作臺‘,
    path: ‘workplace‘,
  }, {
    name: ‘測試頁‘,
    path: ‘test‘,
  }],
}, {
  // 更多配置
}];

加好後,會默認生成相關的路由及導航。

2.2.2、新增布局模板

  如果提供的布局不能滿足你的要求,就需要自己新建 Layout 模板了。假設有兩個新的頁面需要使用新模板,你需要先配置好路由及菜單:

1》路由設置

// src/common/router.js
‘/new‘: {
  component: dynamicWrapper(app, [‘monitor‘], () => import(‘../layouts/NewLayout‘)),
},
‘/new/page1‘: {
  component: dynamicWrapper(app, [‘monitor‘], () => import(‘../routes/New/Page1‘)),
},
‘/new/page2‘: {
  component: dynamicWrapper(app, [‘monitor‘], () => import(‘../routes/New/Page2‘)),
},

2》菜單設置

// src/common/menu.js
const menuData = [{
  name: ‘新布局‘,
  icon: ‘table‘,
  path: ‘new‘,
  children: [{
    name: ‘頁面一‘,
    path: ‘page1‘,
  }, {
    name: ‘頁面二‘,
    path: ‘page2‘,
  }],
}, {
  // 更多配置
}];

在根路由中增加這組新模板:

// src/router.js
<Router history={history}>
  <Switch>
    <Route path="/new" render={props => <NewLayout {...props} />} />
    <Route path="/user" render={props => <UserLayout {...props} />} />
    <Route path="/" render={props => <BasicLayout {...props} />} />
  </Switch>
</Router>

然後在你的新模板中,仿照 src/layouts/BasicLayout.jssrc/layouts/UserLayout.js 生成路由列表即可。

2.3、帶參數的路由/菜單

腳手架默認支持帶參數的路由、菜單及面包屑配置,直接在路由的 key 以及菜單中的 path 配置即可:

1》路由

// src/common/router.js
‘/dashboard/:workplace‘: {
  component: dynamicWrapper(app, [‘chart‘], () => import(‘../routes/Dashboard/Workplace‘)),
},
‘/:list/table-list‘: {
  component: dynamicWrapper(app, [‘rule‘], () => import(‘../routes/List/TableList‘)),
},

2》菜單

// src/common/menu.js
const menuData = [{
  name: ‘dashboard‘,
  icon: ‘dashboard‘,
  path: ‘dashboard‘,
  children: [{
    name: ‘分析頁‘,
    path: ‘analysis‘,
  }, {
    name: ‘監控頁‘,
    path: ‘monitor‘,
  }, {
    name: ‘工作臺‘,
    path: ‘:workplace‘,
  }],
}, {
  name: ‘列表頁‘,
  icon: ‘table‘,
  path: ‘:list‘,
  children: [],
}, {
  // 更多配置
}];

2.4、嵌套布局

  有時在當前 layout 下還需要嵌套其他布局,例如有幾個頁面都需要展示同一個模塊,你可以把這部分提煉出來變成一個新的布局,再到該布局下生成路由列表。與新增布局模板 的區別,只是不需要將它增加到根路由中。具體可以參照 src/common/router.js /list/search 相關配置,及相關組件文件。

2.5、嵌套路由同級展示

  腳手架默認使用工具函數 getRoutes 對 routerData 進行處理,然後生成路由列表,根據基本算法,在每一級組件中只會渲染當前 match.path 下最鄰近的路由,所以,如果你要實現嵌套路由的同級展示(如:將 /list/search/list/search/projects 在同一個地方渲染),就需要手動獲取該路由的數據並添加在合適的地方。

{/* src/layouts/BasicLayout.js 類比你的上層 layout 組件 */}
<Content style={{ margin: ‘24px 24px 0‘, height: ‘100%‘ }}>
  <div style={{ minHeight: ‘calc(100vh - 260px)‘ }}>
    <Switch>
      {/* 默認生成的路由列表,不包含 /list/search/projects */}
      {
        getRoutes(match.path, routerData).map(item => (
          <Route
            key={item.key}
            path={item.path}
            component={item.component}
            exact={item.exact}
          />
        ))
      }
      {/* 補充 /list/search/projects 的路由 */}
      <Route exact path="/list/search/projects" component={routerData[‘/list/search/projects‘].component} />
      <Redirect exact from="/" to="/dashboard/analysis" />
      <Route render={NotFound} />
    </Switch>
  </div>
</Content>

同時在嵌套 layout 的文件中去掉這一條路由(如果還有下層路由需要 render)。

{/* src/routes/List/List.js 類比你的嵌套 layout 組件 */}
<Switch>
  {
    getRoutes(match.path, routerData).filter(item => item.path !== ‘/list/search/projects‘).map(item =>
      (
        <Route
          key={item.key}
          path={item.path}
          component={item.component}
          exact={item.exact}
        />
      )
    )
  }
</Switch>

2.6、隱藏菜單

如果需要隱藏某條菜單項,可以在該條數據中增加 hideInMenu 參數。

hideInMenu: true,  // 隱藏該條,或隱藏該組

2.7、隱藏面包屑

如需隱藏面包屑中的某個層級,可以增加 hideInBreadcrumb 屬性。

// src/common/router.js
‘/dashboard/analysis‘: {
  component: dynamicWrapper(app, [‘chart‘], () => import(‘../routes/Dashboard/Analysis‘)),
  hideInBreadcrumb: true,  // 隱藏該條
},
‘/dashboard/monitor‘: {
  component: dynamicWrapper(app, [‘monitor‘], () => import(‘../routes/Dashboard/Monitor‘)),
},

2.8、關於 dynamicWrapper

import dynamic from ‘dva/dynamic‘;

// wrapper of dynamic
const dynamicWrapper = (app, models, component) => dynamic({
  app,
  // eslint-disable-next-line no-underscore-dangle
  models: () => models.filter(m => !app._models.some(({ namespace }) => namespace === m)).map(m => import(`../models/${m}.js`)),
  // add routerData prop
  component: () => {
    const routerData = getRouterData(app);
    return component().then((raw) => {
      const Component = raw.default || raw;
      return props => <Component {...props} routerData={routerData} />;
    });
  },
});import dynamic from ‘dva/dynamic‘;

// wrapper of dynamic
const dynamicWrapper = (app, models, component) => dynamic({
  app,
  // eslint-disable-next-line no-underscore-dangle
  models: () => models.filter(m => !app._models.some(({ namespace }) => namespace === m)).map(m => import(`../models/${m}.js`)),
  // add routerData prop
  component: () => {
    const routerData = getRouterData(app);
    return component().then((raw) => {
      const Component = raw.default || raw;
      return props => <Component {...props} routerData={routerData} />;
    });
  },
});

為了代碼的簡潔性,我們對 dva/dynamic 進行了二次封裝,需要註意的是這裏使用了 Webpack Code Splitting 的動態 importdvadynamic 方法已經幫我們封裝好了 Promise 動態加載的相關事宜,所以我們只需要直接使用即可。

003-ant design pro 路由和菜單