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 類型確定了,params
和query
就能有字段的提示和檢查了, - 如果內部有子組件
<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... .