是時候放棄react-router,擁抱route狀態化了

前端路由設置一般分爲 2 類:html

集中配置式路由

這也是早些時候用得最多的方式,好比:前端

{
  "routes": [
    {"path": "/", "component": "./a"},
    {"path": "/list", "component": "./b", "Routes": ["./routes/PrivateRoute.js"]},
    {
      "path": "/users",
      "component": "./users/_layout",
      "routes": [
        {"path": "/users/detail", "component": "./users/detail"},
        {"path": "/users/:id", "component": "./users/id"}
      ]
    }
  ]
}
複製代碼

甚至有的框架把路由和請求數據融合到一塊兒,好比:react

const Routes = [
  {
    path: '/',
    exact: true,
    component: Home,
  },
  {
    path: '/posts',
    component: Posts,
    loadData: () => loadData('posts'),
  },
  {
    path: '/todos',
    component: Todos,
    loadData: () => loadData('todos'),
  },
  {
    component: NotFound,
  },
];
複製代碼

上面都是比較簡單的例子,在真實項目中路由每每還和業務邏輯息息相關,獲取數據還有前置依賴和衝突,再考慮到路由權限、邏輯重用、模塊化、路由守護、條件判斷等等,讓集中配置式路由不堪重負,到後面寫出來到配置文件誰都看不明白了。json

動態組件式路由

典型到案例就是 react-router,它將路由邏輯分散在各個組件中變成一種特殊的路由組件,組件在生命週期中一邊運行一邊動態生成路由規則。例如:redux

<Switch>
  <Route exact path="/admin/home" component="{AdminHome}" />
  <Route exact path="/admin/role/:listView" component="{AdminRole}" />
  <Route path="/admin/member/:listView" component="{AdminMember}" />
</Switch>
複製代碼

這樣一來路由及業務邏輯被分散在各個相關組件中,有效的分解了集中配置式的複雜度,看上去很優雅,但也有很多新問題:小程序

  • 首先路由變得隱晦、不直觀了,修改變化起來比較麻煩
  • 將路由綁定到組件,render 再也不純粹,包含了外部環境的反作用
  • 將 path 硬編碼到組件中,不利於後期修改
  • path 做爲一個 string 類型,失去了類型推斷與檢查
  • 對於重定向等等,非得動態執行到後面才知到,等於前面但渲染白白浪費了
  • 最後這種方式難以跨平臺與框架,好比沒法應用到服務器渲染 SSR 中,也沒法應用到不支持動態組件到某些環境中好比:小程序、VUE 等

探索更好路由方案

既然集中配置式動態組件式路由各有優點和劣勢,那有沒有更好等辦法來融合 2 者呢?其實它們的最大的問題都在於路由承擔了過多的職能(路由、取數據、業務邏輯、權限、守護、組件生命週期...)瀏覽器

迴歸到路由的本質:bash

  • 從內部來看:路由只不過就是應用提供一種方法,用來保存記錄應用的某個視角切片。 對於沒有配置路由的單頁應用來講,整個應用就只有一個切片,路由規則定義得越多越細,系統可展現的內部切片也越多越細。因此路由就是一種記錄應用狀態的狀態。服務器

  • 從外界來看:路由只不過就是應用提供一種方法,讓外界(用戶)能夠申請訪問哪些視圖。 那麼從這個角度說,路由無非就是攜帶了 2 個信息:react-router

    • 申請展現哪些視圖。也就是視圖名稱
    • 如何展現它們。也就是展現它們須要提供的參數

咱們在路由中提取出這 2 筆信息後,直接將它們注入到咱們的狀態管理,至此路由的職能也就算完成了,接下來的事情就是狀態管理的事情,與路由無關了。

好比對於一個URL:

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

咱們能夠從中間提取到這樣到信息,並將它注入redux中:

{
  "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
    }
  }
}
複製代碼

路由狀態化

將路由當成是一種跟 Redux 同樣記錄着應用的狀態。只不過 Redux 是記錄在內存中由用戶經過界面交互觸發維護,而 Route 是記錄在瀏覽器地址欄中,由用戶手工輸入維護,它們本質都是同樣的。

因此在將路由轉換爲狀態以後就沒有路由什麼事了,在組件中你直接用狀態控制視圖的顯示便可:

<Switch>
  {routeViews.adminHome?.Main && <AdminHome />}
  {routeViews.adminRole?.List && <AdminRole />}
  {routeViews.adminMember?.List && <AdminMember />}
 </Switch>
複製代碼

結論

通過以上對路由職能的單一化,路由邏輯變成很薄的一層,那咱們還須要 react-router 嗎?還須要路由組件嗎?還要啥自行車?沒那麼複雜的心智負擔了,迴歸到簡單的 MVVM 便可,用個抽象的公式來表示:

  • State = Combine(Route)
  • UI = Render(State)

最後問題又來了,雖然路由職能變得單一,但仍是要履行職能啊,怎麼將路由變成爲狀態呢?也就是怎麼從一串 URL 字符中提取到前面所說的:展現哪些視圖傳遞哪些參數,這 2 筆信息呢?

這個你固然能夠本身去設計你的路由方案,我也設計了一種:@medux/route-plan-a,能夠看看個人開源框架:Medux

Medux 框架介紹

以上僅表明我的探索,歡迎拍磚交流

相關文章
相關標籤/搜索