@medux 路由篇

歡迎您開始@medux 之旅,建議您依次閱讀如下 4 篇文章,這將耗費您大約 30 分鐘。javascript

第 3 篇:medux 路由java

-- Github 地址 ---react

上篇闡述了 medux 路由的基本思路,提到 medux 將路由及參數視爲另外一種 Store,它跟 Redux 的 Store 同樣影響着 UI 的展現,並且 medux 建議您在編寫 component 時忘掉路由的概念,下面結合一個具體實現方案 @medux/route-plan-a  詳細解釋一下:git

關於@medux/route-plan-a

@medux/route-plan-a  是一套基於 @medux/core 實現的跨平臺路由方案,它能夠將 web 的路由風格帶入其它平臺github

關於經常使用概念及名詞

RouteData

一般路由解析及 history 功能由宿主平臺提供,不一樣平臺的路由方案不盡相同,medux 定義了統一通用的路由數據結構 RouteData,框架將自動把 原生路由信息 轉換爲 medux 使用的RouteDataweb

interface RouteData {
  // views 表示當前路由下要展現哪些view
  views: {[moduleName: string]: {[viewName: string]: boolean}};
  // paths 表示當前路由下view的嵌套父子關係
  paths: string[];
  // params 表示當前路由保存(傳遞)了哪些狀態
  params: {[moduleName: string]: {[key: string]: any}};
  // 當原生路由支持多history棧時,每一個棧上分別保存(傳遞)了哪些狀態
  // 常見的web或是小程序都只支持一個history棧,APP支持多棧
  stackParams: {[moduleName: string]: {[key: string]: any}}[];
}
複製代碼

Location

咱們把宿主原生路由信息命名爲 Location,例如在 web 環境中:typescript

interface BrowserLocation {
  pathname: string;
  search: string;
  hash: string;
  state: any;
}
複製代碼

因爲其中的 state 可能包含反作用,因此本方案將其排除,也就是說在本方案中「你不能使用瀏覽器的 state 來存放數據」,因此請忘掉 state 吧:json

interface MeduxLocation {
  pathname: string;
  search: string;
  hash: string;
}
複製代碼

RoutePayload

有的時候,咱們須要基於當前 RouteData 並修改其中的某些值來建立一個 RouteData,這種狀況下咱們能夠簡化 RouteData 的書寫:小程序

interface RoutePayload<P> {
  extend?: RouteData; // 基於一個RouteData
  params?: DeepPartial<P>; //修改其中的某些值
  paths?: string[]; //修改其中的paths
}
複製代碼

TransformRoute

本方案建立了一個轉換器,在 Location 和 RouteData 之間轉換:api

interface TransformRoute {
  locationToRoute: (location: MeduxLocation) => RouteData;
  routeToLocation: (routeData: RouteData) => MeduxLocation;
}
複製代碼

HistoryActions

咱們都很熟悉 Web 中的歷史記錄操做,好比有 push、replace 等,既然 Location 和 RouteData 能夠相互轉換,那麼相應的咱們能夠更靈活的使用它們:

interface HistoryActions<P = {}> {
  listen(listener: LocationListener): UnregisterCallback;
  getLocation(): MeduxLocation;
  getRouteData(): RouteData;
  push(data: RoutePayload | MeduxLocation | string): void;
  replace(data: RoutePayload | MeduxLocation | string): void;
  go(n: number): void;
  goBack(): void;
  goForward(): void;
}
複製代碼

ToBrowserUrl

咱們建立了一個方法直接將 RoutePayload 生成 url:

interface ToBrowserUrl {
  (routeOptions: RoutePayload): string;
  (pathname: string, search: string, hash: string): string;
}
複製代碼

RouteConfig

說了這麼多,咱們將 Location 與 RouteData 轉換的規則是什麼呢?這就是路由配置文件:

interface RouteConfig {
  [path: string]: string | [string, RouteConfig];
}
複製代碼

代碼示例:

const routeConfig = {
  '/$': '@./admin/home', //$結尾爲精確匹配,@開頭表示redirect
  '/': [
    'app.Main',
    {
      '/login': 'app.LoginPage',
      '/admin$': '@./admin/home', //$結尾爲精確匹配,@開頭表示redirect
      '/admin': [
        'adminLayout.Main',
        {
          '/admin/home': 'adminHome.Main',
          '/admin/role/:listView': [
            'adminRole.List',
            {
              '/admin/role/:listView/:itemView/:itemId': 'adminRole.Detail',
            },
          ],
        },
      ],
      '/article$': '@./article/home',
      '/article': [
        'articleLayout.Main',
        {
          '/article/home': 'articleHome.Main',
          '/article/about': 'articleAbout.Main',
        },
      ],
    },
  ],
};
複製代碼

RouteConfig 是一個遞歸對象,它的 key 表示匹配規則 rule,對應的值爲 viewName 或者數組 [viewName, RouteConfig]

  • 當值爲 viewName 時表示若是當前 pathname 匹配了該 rule,就展現該 view,解析到此結束
  • 當值爲數組 [viewName, RouteConfig] 時表示不只要展現該 view,還要繼續往下解析子級匹配
  • viewName 一般使用 ModuleName.ViewName 格式
  • 當 viewName 以@開頭時,表示一個 redirect 跳轉
  • 當 rule 以$結尾時,表示精確匹配,一般用來作重定向 redirect 跳轉

假設當前 url 爲 /admin/role/list 根據以上配置規則,能夠解析得出 RouteData:

{
  "views": {
    "app": {"Main": true},
    "adminLayout": {"Main": true},
    "adminRole": {"List": true}
  },
  "paths": ["app.Main", "adminLayout.Main", "adminRole.List"],
  "params": {
    "app": {},
    "adminLayout": {},
    "adminRole": {"listView": "list"}
  }
}
複製代碼

關於解析方案

利用 pathname 傳遞參數

以上示例子中 params 參數:adminRole: {listView: "list"} 來自於 pathname 對 rule /admin/role/:listView的匹配,因此 pathname 中能夠傳遞參數,它們會被提取到 params 中,而 params 則會以 moduleName 做爲命名空間。

那若是將 RouteConfig 中規則 /admin/role/:listView 改成 /admin/role/:listView.name,解析後你會看到這樣的變化:

adminRole: {listView: "list"} 變成了 adminRole: {listView: {name: "list"}}

也就是說 path 中不只能夠傳遞的參數,還能夠結構化,能夠多層級。

利用 search string 傳遞參數

利用 pathname 只能傳遞簡單的 string 參數。咱們知道一般 url 中傳遞參數是利用 search,好比 /admin/role/list?title=medux&page=1&pagesize=20

在本方案中咱們也能夠利用 search 來傳遞複雜參數,只不過是直接將 json 字符串放入 search 參數中,好比:

/admin/role/list?q={adminRole: {title: "medux", page: 1, pageSize: 20}}

利用 hash string 傳遞私有參數

本方案中 hash 也跟 search 同樣能夠傳遞複雜參數,可是因爲它不會被瀏覽器發送到服務器,因此咱們專門用來存儲一些帶下劃線私有參數,例如:

/admin/role/list?q={adminRole: {title: "medux", page: 1, pageSize: 20}}#q={adminRole: {_random: 34532324}}

  • hash 中參數方式傳遞與 search 同樣
  • hash 專門用來傳遞不發往服務器的私有數據,因此強制其數據名使用_前綴

利用 defaultParams 傳遞默認參數

咱們還能夠爲每一個 module 預先定義一組參數的默認值,好比:

{
  "adminRole": {
    "page": 1,
    "pageSize": 20,
    "sortBy": "createTime"
  }
}
複製代碼

合併各路參數

因此依據本方案,pathname、search、hash、defaultParams 均可以傳遞結構化的參數,最終它們會被合併放入 RouteData 的 params 中,因此最終你能夠看到的 RouteData 以下

{
  "views": {
    "app": {"Main": true},
    "adminLayout": {"Main": true},
    "adminRole": {"List": true}
  },
  "paths": ["app.Main", "adminLayout.Main", "adminRole.List"],
  "params": {
    "app": {},
    "adminLayout": {},
    "adminRole": {
      "listView": "list",
      "title": "medux",
      "page": 1,
      "pageSize": 20,
      "sortBy": "createTime",
      "_random": 34532324
    }
  }
}
複製代碼

RouteData 轉換爲 Location

以上闡述的是怎麼將一個 Location 轉換爲 RouteData,那麼反過來天然也可將 RouteData 轉換爲 Location。

  • 因爲 RouteData 中的 views 其實能夠利用 paths 求出,因此 views 其實能夠省略不傳
  • params 中帶_前綴的數據項會自動放入 hash 中
  • 與默認參數相同的數據項會被排除
const url = toBrowserUrl({
  paths: ['app.Main', 'adminLayout.Main', 'adminRole.List'],
  params: {
    adminRole: {
      listView: 'list', //被pathname匹配到的數據項會放入pathname
      title: 'medux',
      page: 1, //與默認參數相同的數據項將會被排除
      pageSize: 20, //與默認參數相同的數據項將會被排除
      sortBy: 'createTime', //與默認參數相同的數據項將會被排除
      _random: 34532324, //帶_的數據項將放入hash中
    },
  },
});
複製代碼

因此以上代碼將獲得:

/admin/role/list?q={adminRole: {title: "medux"}}#q={adminRole: {_random: 34532324}}

忘掉路由,一切都是 state

能夠看到咱們的 RouteData 中的 params 都是以 moduleName 做爲命名空間的,由於咱們原本就但願將 Route 視爲一個 Store。如今讓咱們把 RouteState 合併到 ReduxState 中,並將 params 注入到 moduleState 中,最終的 RootState 多是這樣

{
  route: {
    location: {
      pathname: "/admin/role/list",
      search: '?q={adminRole: {title: "medux"}}',
      hash: '#q={adminRole: {_random: 34532324}}'
    },
    data: {
      views: {
        app: {Main: true},
        adminLayout: {Main: true},
        adminRole: {List: true},
      },
      paths: ['app.Main','adminLayout.Main','adminRole.List'],
      params: {
        app: {},
        adminLayout: {},
        adminRole: {
          listView: "list",
          title: "medux",
          page: 1,
          pageSize: 20,
          sortBy: "createTime",
          _random: 34532324
        }
      }
    }
  },
  app: {
    routeParams: {},
    ...
  },
  adminLayout: {
    routeParams: {},
    ...
  },
  adminRole: {
    routeParams: { // 已經將RouteState中的狀態注入到對應的模塊中
      listView: "list",
      title: "medux",
      page: 1,
      pageSize: 20,
      sortBy: "createTime",
      _random: 34532324
    },
    ...
  }
}
複製代碼

那麼此時,你在 Component 裏面使用 moduleState 時已經不須要思考它的來源是哪裏了,也許是路由解析得出的,但也沒準是 dispatch action 獲得的呢。

CoreAPI

查看 CoreAPI 文檔

Demo

medux-react-admin:基於@medux/react-web-router和最新的ANTD 4.x開發的通用後臺管理系統,除了演示 medux 怎麼使用,它還創造了很多獨特的理念

繼續閱讀下一篇

@medux 數據流

相關文章
相關標籤/搜索