頻道欄目
首頁 > 程序開發 > web前端 > HTML/CSS > 正文
積夢前端的路由方案ruled-router-題葉-SegmentFault思否
2019-05-11 22:05:10           
收藏   我要投稿

積夢(https://jimeng.io) 是一個為制造業制作的一個平臺.
積夢的前端基于 React 做開發的. 早期使用 React Router.
后來出現了一些 TypeScript 集成還有定制化的需求, 自己探索了一套方案.

使用 React Router 遇到的問題

React Router 本身是一個較為穩妥而且全面的方案, 早期我們使用了它.
后面隨著積夢數據平臺的頁面的重構, 遇到了一些問題.
積夢的管理界面從頂層往內存在多個層級, 復雜的情況會出現五六層嵌套,

導航欄 -> 子導航 -> 標簽頁 -> 功能 -> 子頁面

雖然一般的情況只是三四個層級, 但是頁面的嵌套大量存在,
早期的我們辦法是定義一個 basePath 變量用來表示外層路由,

<Route
  path={`${this.props.basePath}/:page`}
  render={(props) => {
    return this.renderSubPage(props.match.params.page);
  }}
/>

然后在內部跳轉時, 也會使用 basePath 變量快速生成路徑,

<Redirect to={`${this.props.basePath}/${EWorkflowPage.Step}`} />

這樣手動傳遞偶爾會出錯, 特別是當頁面結構發生一些修改的時候.
經過一兩次導航的重構, 我們在局部出現了一些代碼, 無法正確跳轉.
雖然靠著測試逐步修復了問題, 但是隨著頁面增多, 這個問題不能輕視.

我覺得這個問題是兩部分,

一方面是 TypeScript 的類型檢查沒有幫助到的路由部分,
React Router 當中基本上通過字符串定義的路徑, 這些不容易被類型檢查.
特別是拼接的路由, 發生改變以后就難以準確追蹤了.

另一方面, 我認為 React Router 的規則也限制了 JavaScript 代碼的使用.
相對于 React Router 通過 Context 傳遞路由狀態的方案, 更傾向于代碼.
基于 switch/case 還有函數組成的控制流, 有更為靈活的應對的辦法.

路由的解析 ruled-router

我同事和我都有一些使用基于路由配置生成路由的經驗, 商量后我打算嘗試.
我的想法是定義路由規則, 然后將路由解析稱為對象, 然后通過代碼進行控制.

比如這樣一個路徑:

/plants/152883204915/qualityManagement/measurementData/components/21712526851768321/processes/39125230470234114

進行拆解以后我認為就是幾個層級:

/plants/152883204915
 /qualityManagement
  /measurementData/components/21712526851768321/processes/39125230470234114

跟 React Router 直接用標簽做匹配的寫法不同, 我認為路由應該先被解析,
該路由包含了頁面的信息, 也包含了響應的參數, 實際上對應一個鏈表, 用對象表示是:

{
  "name": "plants", // <--- 第一層路由
  "matches": true,
  "restPath": null,
  "data": {
    "plantId": "152883204915"
  },
  "query": {}, 
  "next": {
    "name": "qualityManagement", // <--- 第二層路由
    "matches": true,
    "restPath": null,
    "data": {},
    "query": {},
    "next": {
      "name": "measurementData", // <--- 第三層路由
      "matches": true,
      "restPath": null,
      "data": {
        "componentId": "21712526851768321",
        "processId": "39125230470234114"
      },
      "query": {},
      "next": null
    }
  }
}

這是一個比較清晰的層級的結構, 很容易用 switch/case 判斷渲染對應的子頁面.

而解析這個路由所需要的規則, 也可以通過大致這樣的代碼定義出來.

let pageRules = [
  {
    path: "plants/:plantId",
    next: [
      {
        path: "qualityManagement",
        next: [
          {
            path: "measurementData/components/:componentId/processes/:processId"
          }
        ]
      }
    ]
  },
];

這樣基于路由規則和解析函數, 路由定位的方案就變成了:

從 URL 改變的事件獲取到 location.hash 的字符串, 用函數解析得到路由信息的 JSON 樹, 根據 JSON 逐級傳遞, 用 switch/case 跳轉到對應的頁面.

示例代碼比如:

render() {
  const nextRoute = this.props.route.next;

  switch (nextRoute && nextRoute.name) {
    case RouteOutgoing.Records:
      return <Records route={nextRoute.next} plantId={plantId} />;
    case RouteOutgoing.Settings:
      return <Settings route={nextRoute} />;
  }

  return (
    <Redirect
      to={router.getPath(RouteOutgoing.Records, {
        plantId,
      })}
    />
  );
}

解析的代碼在 ruled-router 可以找到, 使用 TypeScript 開發, 有基礎的類型約束.

從代碼看, 由于路由層級的顯式處理, 會存在不少的 .next 需要手工維護, 對于維護有些啰嗦.
當然這個寫法好的一面是路由信息隨時可以打印和調試, 方便定位問題.

路由的跳轉(code generator)

在 React Router 當中路由的跳轉相對簡單, 提供路徑的字符串表示即可完成:

history.push('/a/b/${c}/d')

但是前面說了, 這樣無法進行類型檢測, 無法定位出現問題的路由位置.
我們嘗試了幾個方案, 用比較多的一個方案是給路由定義唯一的 ID 的枚舉值, 然后查找枚舉值跳轉.
后來我從另一個思路開始嘗試, 試著用不同的方案來搭配 TypeScript.

比如說這樣的一套規則, 定義 3 個頁面:

let routeRules = [
  { path: "home" },
  { path: "content" },
  { path: "else" },
  { path: "", name: "home" }
]

那么對應這個路由我就生成響應的代碼, 這段代碼, 就是 TypeScript 可以做類型檢查的了,

export let genRouter = {
  home: {
    name: "home",
    raw: "home",
    path: () => `/home`,
    go: () => switchPath(`/home`),
  },
  content: {
    name: "content",
    raw: "content",
    path: () => `/content`,
    go: () => switchPath(`/content`),
  },
  else: {
    name: "else",
    raw: "else",
    path: () => `/else`,
    go: () => switchPath(`/else`),
  },
  _: {
    name: "home",
    raw: "",
    path: () => `/`,
    go: () => switchPath(`/`),
  },
};

其中 .go() 方法用于跳轉, .path() 方法用于生成其他組件需要的字符串形態.
當然, 維護這樣的一段代碼, 成本并不低, 但是好在這樣高度重復的代碼是可以用代碼生成的,
于是我們增加了 router-code-generator 這個腳本, 用于生成路由代碼.

這樣, 添加新路由的時候就需要,

在 rules 當中添加路由規則, 運行腳本生成路由的代碼, 在需要跳轉的位置引用 genRouter 對象, 調用對應方法進行跳轉.

實際業務當中的代碼當然會復雜很多, 項目最終生成出來是兩千多行的路由文件,

export let genRouter = {
  plants_: {
    name: "plants",
    raw: "plants/:plantId",
    path: (plantId: Id) => `/plants/${plantId}`,
    go: (plantId: Id) => switchPath(`/plants/${plantId}`),
    information: {
      name: "information",
      raw: "information",
      path: (plantId: Id) => `/plants/${plantId}/information`,
      go: (plantId: Id) => switchPath(`/plants/${plantId}/information`),
      products: {
        name: "material.finished",
        raw: "products",
        path: (plantId: Id) => `/plants/${plantId}/information/products`,
        go: (plantId: Id) => switchPath(`/plants/${plantId}/information/products`),

實際項目當中的腳本生成也是個需要處理的地方, 我們用 Webpack 將這部分代碼打包運行,
性能上還好, 關掉類型檢查的話幾秒鐘內可以完成, 具體看示例的代碼:
https://github.com/jimengio/t...

類型檢查的覆蓋

前面的兩部分, 覆蓋了路由的解析, 還有路由的跳轉, 完成了基本的路由的功能.

路由解析部分, 路由規則可以通過 JSON 結構定義, 基本能得到 TypeScript 的提示.
路由的解析結果, 是一棵大的 JSON 的樹, 這中間有不少動態的部分, 需要開發時自己留意.
路由跳轉的代碼, 整個 Object 定義的結構可以被 TypeScript 解析, 基本上有完整的補全.

雖然并不完美, 但是很大程度利用了 TypeScript 的自動補全以及類型檢查簡化了書寫.
當路由有增改時, 通過運行腳本還有執行類型檢查, 比較容易定位到發生改變的部分.

該方案的不足

路由劫持等功能

React Router 提供的功能顯然遠不止解析和跳轉, 還有一些頁面跳轉相關的鉤子, 甚至漸變等效果.
ruled-router 的方案沒有去實現相關的功能.

腳手架比較麻煩

從前面的描述也能看出來, 這一整套寫法, 特別是后面跳轉的寫法, 引入了大量腳手架.
需要專門寫一個 Webpack 配置來生成路由, 一般項目來說覺得非常繁瑣了.

實際在項目當中, 由于我們有著較深的路由層級, 實際代碼看上去又長又啰嗦:

genRouter.plants_.product.batch_.ooc.go(plantId, value);

genRouter.plants_.model.projects._status.go(this.props.plantId, record.id);

這代碼是靠著 VS Code 提供的代碼補全才能很快寫出來... 也就是和 TypeScript 以及 VS Code 等工具綁定死了.

結尾

除了上面介紹的, 其他一些功能也在 ruled-router 方案里做了一些支持:

Query 參數. 可以被解析, 也可以在跳轉代碼當中被生成出來. 基本可用. 只是類型有缺失. 性能優化的問題, 需要配合 shouldComponentUpdate 或者 useMemo 來優化, 就用到易于匹配的字符串形態.

特別是隨著項目規模增加, 幾百個大小頁面的木有, 更多會需要類型檢查工具來幫助我們做校驗.
當然目前的方案在開發當中依然有著細節上的各種需要優化的地方, 要后續再想辦法進一步優化.

點擊復制鏈接 與好友分享!回本站首頁
相關TAG標簽
上一篇:React優化子組件render-個人文章-SegmentFault思否
下一篇:組件中watchprops根據v-if動態判斷并掛載DOM的問題-前端和Node學習筆記-SegmentFault思否
相關文章
圖文推薦
點擊排行

關于我們 | 聯系我們 | 廣告服務 | 投資合作 | 版權申明 | 在線幫助 | 網站地圖 | 作品發布 | Vip技術培訓 | 舉報中心

版權所有: 紅黑聯盟--致力于做實用的IT技術學習網站

加拿大28火车判定方法