Vue自動化路由(基於Vue-Router)開篇
- 2021 年 1 月 8 日
- 筆記
- ======3 前端======, ======3.1 淺入深出Vue, VUE, vue-router, 自動化
vue自動化路由
好久不見~ 若羽又開篇Vue的內容了。
年初的時候發佈了第一版的ea-router
自動化路由庫,歡迎大家安裝使用。[Github地址] [npm地址]
經歷一年的使用。還是發現了不少問題和不足的地方,因此在前段時間抽空整理了所有需求並做了個規劃。並發佈了一個版本。下面來看看其中的原理和實現吧。
前言
因為之前都是寫後端邏輯,因此接觸前端後始終不太習慣js的原生語法。更偏向於es6的class
寫法,並且從ECMAScript後續的標準來看,官方也是比較推薦class
的寫法來更好的組織代碼,並使其具有更強的表義性。哈哈,當然因為更熟悉後者,所以更偏袒一點。
功能需求
功能主要分為兩部分:
- 路由自動化
- 服務於庫的裝飾器
路由自動化中,除了原有的自動生成外,還增加了另外兩個在業務中會經常使用到的功能:
- 設置缺省的Layout
- 設置缺省的404頁面
目錄中的子目錄關係,用路由中嵌套路由來進行表達,因此需要一個入口進行渲染,這就是Layout
存在的一個意義,另外一層則是作為某個模塊的通用布局存在。
裝飾器主要用於補充路由相關特性,比如vue-router
中的各種特性(命名路由,別名,params等等),無縫的接入業務中。就像vue-property-decorators
庫一樣。
原理
為了達成自動化路由的目的,本質就是要將路由對象按照某種特定的規則進行生成即可。那麼參考後端MVC中的路由
以及其他前端路由框架,將需要路由的頁面按照目錄的層次結構進行組織,然後對目錄進行解析是比較通用並容易實現的。
- 掃描目錄文件
- 還原目錄結構
- 轉換為目錄對象
- 加載適配器(默認為
vue-router
的適配器) - 適配器將目錄對象轉換為
routes
- 使用
routes
目錄對象
將實際的目錄結構映射成對象,下面看一個例子:
目錄結構如下:
views
|-- About.vue
|-- Home.vue
|-- Layout.vue
|-- user
|-- Add.vue
router/index.js
代碼如下:
// /src/router/index.js
import Vue from "vue";
import VueRouter from "vue-router";
import AutoRouteGenerator from "ea-router";
import defaultLayout from "../components/defaultLayout";
import notFoundPage from "../components/notFound";
Vue.use(VueRouter);
let generator = new AutoRouteGenerator(
require.context("../views", true, /\.vue$/)
);
generator.setDefaultLayout(defaultLayout);
generator.setNotFoundPage(notFoundPage);
const routes = generator.generate();
const router = new VueRouter({
routes
});
export default router;
對應vue-router
,自動生成的路由對象會是如下形式(裏面的對象是自動生成的,導出語句不是喔,只是為了演示):
const routes = [
{
path: "/",
component: () => import("src/views/layout.vue"),
children: [
{
path: "home/:id/:name",
component: () => import("src/views/home.vue"),
props: true
},
{
path: "about",
component: () => import("src/views/about.vue")
},
{
path: "user",
component: () => import("src/components/defaultLayout.vue"),
children: [
{
path: "add",
component: () => import("src/views/user/add.vue")
}
]
}
]
},
{
path: "*",
component: () => import("src/components/notFound.vue")
}
];
export default routes;
因為使用的是webpack
的require.context
函數,但是它有一個缺陷就是掃描出來的並不是目錄原來的層次結構。而是一維的結構,因此我們首先要還原原來的層次結構,並在此基礎上封裝、解析一些必要的信息。
適配器
適配者模式在這個場景下非常合適,輸入是解析後的目錄對象,而輸出則是變化的。有可能是:
vue-router
的路由對象routes
vue-router-next
的路由對象routes
- 其他路由框架的路由對象
想要適配其他框架, 則只需要實現對應的適配器並加載即可。
使用
目前有3個api以及5個裝飾器
api:
decorators:
generate
api
構造函數中傳入通過require.context
指定目錄及過濾規則, 如下實例是指定views
目錄下所有.vue
文件。
路由生成的api, 調用此方法將生成一個對應 路由適配器 生成的路由對象,目前默認內置的時基於vue 2.x
的vue-router
。
// src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import RouteGenerator from "ea-router";
Vue.use(Router)
let generator = new RouteGenerator(require.context('./views', true, /\.vue$/))
export default new Router({
routes: generator.generate()
})
那麼在 main.js
中,我們不用改動原有的代碼即可直接使用:
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
setDefaultLayout
api
指定默認的Layout.vue
,因為在大多數情況下,Layout.vue
的內容可能都是下面這樣:
<template>
<router-view></router-view>
</template>
這種情況下,Layout.vue
的目的僅僅是作為子路由的入口。那麼我們可以直接利用setDefaultLayout
來設置默認的Layout
。
規則如下:
- 噹噹前目錄中沒有
Layout.vue
時,會嘗試使用設置的默認Layout
。 - 當沒有
Layout.vue
並且沒有設置默認Layout
時,將會拋出異常。
實例:
// src/router.js
import Vue from 'vue'
import Router from 'vue-router'
import RouteGenerator from "ea-router";
import DefaultLayout from './components/defaultLayout.vue';
Vue.use(Router)
let generator = new RouteGenerator(require.context('./views', true, /\.vue$/))
generator.setDefaultLayout(DefaultLayout);
export default new Router({
routes: generator.generate()
})
<!-- /src/components/defaultLayout.vue -->
<template>
<router-view></router-view>
</template>
setNotFoundPage
api
設置路由匹配失敗時顯示的頁面。
實例:
// src/router.js
import Vue from 'vue'
import Router from 'vue-router'
import RouteGenerator from "ea-router";
import NotFoundPage from './components/notFound.vue';
Vue.use(Router)
let generator = new RouteGenerator(require.context('./views', true, /\.vue$/))
generator.setNotFoundPage(NotFoundPage);
export default new Router({
routes: generator.generate()
})
<!-- /src/components/notFound.vue -->
<template>
<div>
嘿,頁面走丟啦!
</div>
</template>
@RouteName(name: string)
decorator
設置路由名稱,在vue-router
中對應了命名路由
import { Vue, Component } from 'vue-property-decorator';
import { RouteName } from 'ea-router';
@RouteName('YourComponentRouteName')
@Component
export default class YourComponent extends Vue {
}
等價於
const router = new VueRouter({
routes: [
{
path: 'path/to/component/on/directory',
name: 'YourComponentRouteName',
component: YourComponent,
}
]
})
Note that: path的生成規則是相對路徑噢(根目錄是構造函數中傳入的目錄,示例中也就是src/views
)
@Alias(alias: string)
decorator
設置路由別名,對應vue-router
中的別名
import { Vue, Component } from 'vue-property-decorator';
import { Alias } from 'ea-router';
@Alias('YourComponentAlias')
@Component
export default class YourComponent extends Vue {
}
等價於
const router = new VueRouter({
routes: [
{
path: 'path/to/component/on/directory',
alias: 'YourComponentAlias',
component: YourComponent,
}
]
})
@Context(params: string[])
decorator
設置路由上下文,對應了vue-router
中的$routes.params
會根據傳入的順序生成path
。
import { Vue, Component } from 'vue-property-decorator';
import { Context } from 'ea-router';
@Context('id', 'type')
@Component
export default class YourComponent extends Vue {
}
等價於
const router = new VueRouter({
routes: [
{
path: 'path/to/component/on/directory/:id/:type',
component: YourComponent,
}
]
})
Note that: 如果同時使用 @Alias
和 @Context
, 上下文的參數會自動添加在alias
後面, 就像下面的例子:
import { Vue, Component } from 'vue-property-decorator';
import { Context, Alias } from 'ea-router';
@Alias('YourComponentAlias')
@Context('id', 'type')
@Component
export default class YourComponent extends Vue {
}
等價於
const router = new VueRouter({
routes: [
{
path: 'path/to/component/on/directory/:id/:type',
alias:'YourComponentAlias/:id/:type',
component: YourComponent,
}
]
})
@EnableProps()
decorator
開啟路由參數的Boolean
模式, 對應了vue-router
中的路由傳參-布爾模式
import { Vue, Component } from 'vue-property-decorator';
import { EnableProps } from 'ea-router';
@EnableProps()
@Component
export default class YourComponent extends Vue {
}
等價於
const router = new VueRouter({
routes: [
{
path: 'path/to/component/on/directory',
props: true,
component: YourComponent,
}
]
})
Note that: 一般搭配 @Context
使用。
@Meta(meta: object)
decorator
設置路由元信息,對應vue-router
中的路由元信息
import { Vue, Component } from 'vue-property-decorator';
import { Meta } from 'ea-router';
@Meta({
title: 'Component Title',
requireAuthorize: true
})
@Component
export default class YourComponent extends Vue {
}
等價於
const router = new VueRouter({
routes: [
{
path: 'path/to/component/on/directory',
component: YourComponent,
meta: {
title: 'Component Title',
requireAuthorize: true
},
}
]
})
建議
在開發過程中,使用Class
形式的寫法是最為推薦的,表義性和組織性會更強一些。配合vue-property-decorators
食用更佳喔。好了,接下來說正經的:
- 路由跳轉,建議使用命名路由的跳轉方式。去查看相關文檔
- 給路由命名,並統一定義路由的名稱,便於管理(如,都定義在
/src/domain/views.js
中)。 - 路由上下文使用
Props
進行傳參。
計劃
- 實現
vue-router-next
的適配器 - 實現路由文件的自動生成(基於模板語法)
- 添加可設置所有選項配置的裝飾器
- 開放加載自定義適配器
- typescript支持
- 回補單元測試
總結
做這個庫之前,也查找了很多相關資料。並且翻了不少類似庫的源碼進行學習,發現比較常見的做法:
- 動態加載,即請求時去
import
實現動態加載。但這個只是做了自動尋找路由,對於路由的組織還是沒有比較好的解決。 webpack
動態解析路徑,通過正則表達式或者vue單文件組件解析器對文件進行解析,提取內容。這種方式非常接近本文中的方式,但是缺點也比較明顯:不支持變量,如果全部硬編碼到文件里,管理也是一個問題。
最後結合大家的經驗,實現了這個庫。下一步也會考慮開始實現生成路由文件,補充這一塊的空白。
關於自動化路由這部分,將會從分析、實現、使用以及後續開發都會記錄下來,並且會開源用了此庫的一些個人項目,形成系列文章。這篇就當是起個頭,如有不足,歡迎各位指正。
~另外歡迎大家使用並提出寶貴的意見喲