ruled-router 生成路由類型細節記錄

ruled-router 是我們(積夢前端)定製的路由方案, 另外強化了類型方面,
之前的介紹可以看文章: 積夢前端的路由方案 ruled-router.

關於跳轉方法的類型

路由生成部分, 大致上就是對於規則:

[
  {
    "name": "a",
    "path": "a",
    "next": [
      {
        "name": "b",
        "path": "b/:id"
      }
    ]
  }
]

會通過腳本生成路由的調用方法, 現在的生成結果是:

export let genRouter = {
  a: {
    name: "a",
    raw: "a",
    path: () => `/a`,
    go: () => switchPath(`/a`),
    b: {
      name: "b",
      raw: "b",
      path: (id: string) => `/a/b/${id}`,
      go: (id: string) => switchPath(`/a/b/${id}`),
    },
  },
};

這樣可以通過調用方法來進行路由跳轉,

genRouter.a.b.go(id)

這個步驟, 是有類型支持的. TypeScript 會檢查整個結構, 不會有錯誤的調用.
也就是說, 所有的調用, 按照這個寫法, 不會導致出現不符合路由規則的路徑.
整個實現模塊維護在 https://github.com/jimengio/r... .

解析結果的類型問題

現在的短板是在解析解析結果的類型上面, 回顧一下 ruled-router 解析的結果,
對於路徑:

/home/plant/123/shop/456/789

按照路由規則做一次解析,

let pageRules = [
  {
    path: "home",
    next: [
      {
        path: "plant/:plantId",
        next: [
          {
            path: "shop/:shopId/:corner"
          }
        ]
      }
    ]
  }
];

會得到一個 JSON 結構,

{
  "raw": "home",
  "name": "home",
  "matches": true,
  "restPath": ["plant", "123", "shop", "456", "789"],
  "params": {},
  "data": {},
  "next": {
    "raw": "plant/:plantId",
    "name": "plant",
    "matches": true,
    "restPath": ["shop", "456", "789"],
    "params": {
      "plantId": "123"
    },
    "data": {
      "plantId": "123"
    },
    "next": {
      "raw": "shop/:shopId/:corner",
      "name": "shop",
      "matches": true,
      "next": null,
      "restPath": [],
      "data": {
        "shopId": "456",
        "corner": "789"
      },
      "params": {
        "plantId": "123",
        "shopId": "456",
        "corner": "789"
      }
    }
  }
}

這個 JSON 結構當中部分字段是固定的, 部分是按照規則定義的參數,
如果用一個類型來表示, 就是:

interface IParsedResult<IParams, IQuery>

這也是我們以往的寫法. 這個寫法比較穩妥, 但是問題就是書寫麻煩,
路由比較多, 需要手寫的 IParams IQuery 比較多, 也難以維護.

當前嘗試生成路由的方案

對於這個問題, 我想到的方案, 主要是能不能像前面一樣把類型都生成出來,
大致想到的是這樣一個方案, 生成一棵嵌套的路由的樹,
https://gist.github.com/cheny...
我需要這棵樹滿足兩個需求,

  • 能得到一個完整的路由, 其中的 next: A | B | C 能羅列所有子路由類型,
  • 我能通過 x.y.z.$type 來獲取其中一棵子樹, 因爲子組件需要具體一個類型,

這個方案最重要的地方就是需要 VS Code 能推斷出類型進行提示,
經過調整以後, 得到一個可用的方案, 基於這樣的規則,

[
  {
    "path": "a",
    "queries": ["a"],
    "next": [
      {
        "path": "b",
        "queries": ["a", "b"]
      },
      {
        "path": "d"
      }
    ]
  }
]

生成的類型文件的是這樣:

export type GenRouterTypeMain = GenRouterTypeTree["a"];

export interface GenRouterTypeTree {
  a: {
    name: "a";
    params: {};
    query: { a: string };
    next: GenRouterTypeTree["a"]["b"] | GenRouterTypeTree["a"]["d"];
    b: {
      name: "b";
      params: {};
      query: { a: string; b: string };
      next: null;
    };
    d: {
      name: "d";
      params: {};
      query: { a: string };
      next: null;
    };
  };
}
  • 頂層的路由

頁面首先會被解析, 得到一個 router 對象

let router: GenRouterTypeMain = parseRoutePath(this.props.location.pathname, pageRules);

router 的類型是 GenRouterTypeMain, 這個類型是頂層的類型,
這個例子當中只有一個頂級路由,

export type GenRouterTypeMain = GenRouterTypeTree["a"];

實際當中更可能是多個可選值, 就像這樣

type GenRouterTypeMain = GenRouterTypeTree["a"] | GenRouterTypeTree["b"] | GenRouterTypeTree["c"];
  • 組件使用的子路由

子組件當中, props.router 的類型對應的是子樹的某一個位置,
這裏的 next 因爲用了 Union Type, 不能直接引用其中某個 case,
就需要通過另一個寫法, 從數據的路徑上直接通過類型訪問, 比如:

GenRouterTypeTree["a"]

更深層的子組件的類型, 比如嵌套的第二層, 就需要用:

GenRouterTypeTree["a"]["b"]

不過這個在組件定義當中並不直接是拿到, 因爲在 props 可能無法確定類型,
就需要通過父級的 next 來訪問, 具體是一個 Union Type:

let InformationIndex: FC<{
  router: GenRouterTypeTree["a"]["next"] }
  // next type
  // GenRouterTypeTree["a"]["b"] | GenRouterTypeTree["a"]["d"]

> = (props) => {
  // TODO
}
  • 配合 VS Code 做類型推斷

爲了能讓 VS Code 從 next 推斷出類型, 需要同 switch 語句判斷,

if (props.router) {
  switch (props.router.name) {
  case "b": // TODO, router: GenRouterTypeTree["a"]["b"]
  case "d": // TODO, router: GenRouterTypeTree["a"]["d"]
  }
}

效果大致上,

  • case 後面的字符串在一定程度上可以自動補全和類型檢查,
  • case 後面, router 類型確定了, paramsquery 就能有字段的提示和檢查了,
  • 如果內部有子組件 <A router={router.next} />, router.next 會被類型檢查.

當然這些主要還是提示的作用, 並不是完全跟 router 對應的類型, 不然結構會更復雜,
我試着在已有的組件項目當中做了嘗試, 包括比鏈接更大的項目, 基本是可用的,
https://github.com/jimengio/m...

其他

目前來說, 能對項目路由進行檢查, 就算是達到了最初的類型的目標,
至少能夠保證, 開發當中, 使用生成的路由, 能提示和檢查 params query 中的字段,
並且提交到倉庫的代碼, CI 當中能檢查到參數, 做一些質量的保證.

case 當中能夠提示字符串, 算是意料之外的一個好處吧.
不過這個也要注意, VS Code 推斷的能力有限, 只能用 switch 這個簡單的寫法,
再複雜一些, 比如嵌套了表達式, 或者往子路由數據再判斷, 就推斷不出來了.

當前比較擔心的是項目當中出現深度嵌套的路由, 加上字段名稱長, 整體會非常長:

GenRouterTypeTree["a"]["d"]["e"]["f"]["g"]

由於我們最大的項目當中曾在深達 6 層的路由, 不能不擔心會出現超長的單行路由...
後面再想想有沒有什麼辦法繼續做優化..


其他關於積夢前端的模塊和工具可以查看我們的 GitHub 主頁 https://github.com/jimengio .
目前團隊正在擴充, 招聘文檔見 GitHub 倉庫 https://github.com/jimengio/h... .

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章