詳解Vue路由自動(dòng)注入實(shí)踐
什么是路由自動(dòng)注入
路由自動(dòng)注入概念學(xué)習(xí)自nuxt,我們不需要在 router.js
中每次手動(dòng)輸入代碼引入模塊而是自動(dòng)根據(jù) 文件目錄格式
生成 router.js
我們把這個(gè)功能獨(dú)立成一個(gè) webpack
插件,并對(duì)相關(guān)功能進(jìn)行了完善,而且實(shí)現(xiàn)了 vue-router
的所有核心功能
更詳細(xì)使用指南和文檔可以查看我們的 github倉(cāng)庫(kù)
舉一個(gè)簡(jiǎn)單的列子,比如你的目錄長(zhǎng)這樣
src ├── views │ ├── Login │ │ └── Index.vue │ └── User │ ├── Account │ │ └── Index.vue │ ├── Home │ │ └── Index.vue │ └── Index.vue
規(guī)則很簡(jiǎn)單,如果目錄的一層是 Index.vue
,則目錄名便是當(dāng)前的路由名字,如果是子文件夾則是第二層路由,之后自動(dòng)生成的 router.js
會(huì)長(zhǎng)成這樣
{ component: () => import('@/views/Login/Index.vue'), name: 'login', path: '/login' }, { component: () => import('@/views/User/Index.vue'), name: 'user', path: '/user' }, { component: () => import('@/views/User/Account/Index.vue'), name: 'user-account', path: '/user/account' }, { component: () => import('@/views/User/Home/Index.vue'), name: 'user-home', path: '/user/home' }
這里值得一提的是其實(shí)生成的 router.js
是沒(méi)有必要加入到版本控制當(dāng)中的,因?yàn)椴徽撛陂_(kāi)發(fā)( development
)還是生產(chǎn)( production
)第一次構(gòu)建項(xiàng)目都會(huì)自動(dòng)生成,比如你項(xiàng)目用到了 git
和 eslint
,那么應(yīng)該把它放在 .gitignore
和 .eslintignore
中
為什么使用路由自動(dòng)注入
方便
不用每次去引用模塊,只用創(chuàng)建文件夾, router.js
會(huì)自動(dòng)生成
統(tǒng)一路由命名
如果有完整的 code review
這個(gè)問(wèn)題是不會(huì)存在的,但我們稍微做了一點(diǎn)簡(jiǎn)便,只要 code review
文件夾的命名就好了,最終生成的路由path會(huì)以駝峰命名,生成的name會(huì)以駝峰命名并且以連字符 -
連接不同層級(jí)的路由
統(tǒng)一路由層級(jí)
如圖片中的列子,我們無(wú)法從文件的命名去判斷路由到底在幾級(jí),而且經(jīng)常寫(xiě)的時(shí)候,明明是2級(jí)或3級(jí)路由卻和1級(jí)路由在一層路由下,這是很不規(guī)范而且與邏輯不符的
對(duì)比一下使用自動(dòng)注入劃分層級(jí)后的路由
src/views ├── Index.vue ├── NotFound.vue ├── Withdraw <!-- 第一級(jí) --> │ ├── Index.vue │ └── Result │ ├── Description <!-- 第三級(jí) --> │ │ └── Index.vue <!-- 第二級(jí) --> │ └── Index.vue └── WithdrawHistory <!-- 第一級(jí) --> └── Index.vue
可以從目錄結(jié)構(gòu)看出路由的層級(jí)
我們?cè)賮?lái)看看生成的路由,不同層級(jí)的路由名字通過(guò)連字符 -
連接,層級(jí)很清晰
{ component: () => import('@/views/Withdraw/Index.vue'), name: 'withdraw', path: '/withdraw' }, { component: () => import('@/views/Withdraw/Result/Index.vue'), name: 'withdraw-result', path: '/withdraw/result' }, { component: () => import('@/views/Withdraw/Result/Description/Index.vue'), name: 'withdraw-result-description', path: '/withdraw/result/description' }, { component: () => import('@/views/WithdrawHistory/Index.vue'), name: 'withdrawHistory', path: '/withdrawHistory' },
為什么選擇 vue-router-invoke-webpack-plugin
完善的單元測(cè)試
types支持
vue-router-invoke-webpack-plugin 中獨(dú)特的路由劃分思維
當(dāng)我們的頁(yè)面過(guò)多的時(shí)候,比如項(xiàng)目有60多個(gè)甚至70多個(gè)單頁(yè)面,文件不可能會(huì)放在一個(gè)目錄下,一般這種時(shí)候,我們會(huì)按 功能
將相似功能的路由放在一個(gè)目錄下,我們之前也是這么做的,其實(shí)這么做也是沒(méi)啥問(wèn)題的,但在路由自動(dòng)注入下,我們提出了另外一種思路按路由 層級(jí)
劃分
什么是層級(jí)劃分呢,簡(jiǎn)單的一句話就是根據(jù)頁(yè)面所在的相對(duì)url地址進(jìn)行劃分,舉個(gè)列子,我們的首頁(yè)如下
首頁(yè)的路由為 /
,我們把首頁(yè)當(dāng)作根路由,那么可以進(jìn)入的一級(jí)路由分別為 提現(xiàn)
提現(xiàn)記錄
分成數(shù)據(jù)
等,點(diǎn)擊提現(xiàn)后,我們進(jìn)入了提現(xiàn)路由 /withdraw
進(jìn)入提現(xiàn)頁(yè)面后,會(huì)有兩處可點(diǎn)擊,這兩處便是二級(jí)頁(yè)面,放在一級(jí)頁(yè)面的子文件夾中,按剛才的說(shuō)法,路由目錄(截取部分)便是這樣
src/views ├── Bank <!-- 銀行卡管理 --> │ └── Index.vue ├── DivideData <!-- 分成數(shù)據(jù) --> │ └── Index.vue <!- 首頁(yè) ---> ├── Index.vue <!-- 404路由 --> ├── NotFound.vue ├── Withdraw │ ├── BankDetails <!-- 提現(xiàn)中查看銀行卡信息 --> │ │ └── Index.vue │ ├── Description <!-- 提現(xiàn)說(shuō)明 --> │ │ └── Index.vue <!-- 提現(xiàn)頁(yè)面 --> │ └── Index.vue └── WithdrawHistory <!--提現(xiàn)記錄 --> └── Index.vue
其實(shí)一般這么分下來(lái),相似功能的是會(huì)在一個(gè)文件夾下面的,也實(shí)現(xiàn)了按功能分路由的思路,而且這種層級(jí)劃分是一目了然的,很容易可以看出路由的從屬關(guān)系
但有時(shí)候也會(huì)遇到一個(gè)麻煩,就是有些頁(yè)面可能出現(xiàn)在當(dāng)前層級(jí)下面,也可能出現(xiàn)在另外一個(gè)層級(jí)下面,按功能分的時(shí)候也有這種,就是功能可能存在于兩個(gè)功能點(diǎn)之間,這種情況其實(shí)可以考慮下在哪個(gè)層級(jí)的權(quán)重重一點(diǎn)或者從用戶的點(diǎn)擊習(xí)慣考慮,哪個(gè)位置進(jìn)去會(huì)多一點(diǎn)就放在哪個(gè)層級(jí)下面
vue-router-invoke-webpack-plugin 中獨(dú)特的文件結(jié)構(gòu)
也許大家會(huì)有疑問(wèn),為啥非要寫(xiě)成 Index.vue
并多加一層文件夾封裝,直接命名 vue
文件不好嗎,用過(guò) nuxt
的同學(xué)可能也會(huì)感覺(jué)到這一點(diǎn)的區(qū)別,這也是我們?cè)?nuxt
的基礎(chǔ)上增加的一個(gè) feature
,為了更友好的封裝一個(gè)單頁(yè)面
舉個(gè)列子,如果你的項(xiàng)目沒(méi)有引用ui庫(kù),很多業(yè)務(wù)組件需要自己寫(xiě),除了常用的組件會(huì)放在目錄最外面的 components
文件,其余的對(duì)應(yīng)一個(gè)單頁(yè)面的業(yè)務(wù)組件你會(huì)放在哪里呢,這就是我們預(yù)留的位置,比如一個(gè)目錄結(jié)構(gòu)如下
src/views ├── Audit │ ├── Index.vue │ ├── components │ │ └── AuditItem.vue │ └── images │ └── AuditIntro.png
Audit
是我們的審批頁(yè)面,其中用到了一個(gè)只有當(dāng)前頁(yè)面所用的 AuditItem.vue
組件,也引用了一個(gè)只有當(dāng)前頁(yè)面所用到的圖片 AuditIntro.png
,獨(dú)特的文件結(jié)構(gòu)就是為了這種需求而生的,當(dāng)前頁(yè)面的組件圖片放在一個(gè)文件夾中會(huì)更清晰,但值得一提的是,你也需要在插件中設(shè)置 ignore
去忽略掉不被我們解析的目錄,比如這樣
plugins: [ new VueRouterInvokeWebpackPlugin({ dir: 'src/views', alias: '@/views', language: 'javascript', ignore: ['images', 'components', 'template.vue'] }) ];
那么 images
components
template.vue
會(huì)被忽略不解析
聊一聊路由權(quán)限控制
關(guān)于前端控制路由權(quán)限,前段時(shí)間看到過(guò)一個(gè)文章,感覺(jué)實(shí)現(xiàn)思路稍微復(fù)雜了點(diǎn),其實(shí)有一個(gè)比較簡(jiǎn)單的思路,就是后端給定當(dāng)前用戶沒(méi)有權(quán)限的路由,然后前端在 beforeEach
鉤子中去匹配,如果匹配到?jīng)]有權(quán)限則直接跳404或者沒(méi)有權(quán)限的頁(yè)面就行了,如果用 vue-router-invoke-webpack-plugin
寫(xiě)會(huì)這么寫(xiě)
apis.getForbiddenRoute
export default { // 請(qǐng)求當(dāng)前沒(méi)有權(quán)限的路由列表 async getForbiddenRoute() { return ['/single/user']; } };
plugins: [ new VueRouterInvokePlugin({ // 觀察的目錄 dir: 'demos/src', // 觀察目錄的別名 alias: '@/src', // 當(dāng)前語(yǔ)言 language: 'javascript', // 生成router.js的位置 routerDir: 'demos', // 忽略文件夾 ignore: ['images', 'template.vue', 'components', 'notfound.vue'], // 404路由地址 notFound: '@/src/NotFound.vue', // 引用的模塊 modules: [ { name: 'apis', package: '@/apis' } ], // 同scrollBehavior scrollBehavior: (to, from, savedPosition) => { if (savedPosition) { return savedPosition; } else { return { x: 0, y: 0 }; } }, <!-- 主要是這段代碼 --> /* eslint-disable */ beforeEach: async (to, from, next) => { // 通過(guò)綁定在靜態(tài)屬性上的_cachedForbiddenRoute判斷是否請(qǐng)求過(guò)接口 if (!Vue._cachedForbiddenRoute) { Vue._cachedForbiddenRoute = []; await apis.getForbiddenRoute().then(res => { Vue._cachedForbiddenRoute = res; }); } // 當(dāng)當(dāng)前頁(yè)面的地址存在于禁止訪問(wèn)的列表中,則直接跳轉(zhuǎn)到404頁(yè)面 if (Vue._cachedForbiddenRoute.includes(to.path)) { next({ name: 'notFound' }); } else { next(); } } }), ]
但話說(shuō)回來(lái),任何實(shí)現(xiàn)思路,前端獲取的接口數(shù)據(jù)想篡改還是能繞過(guò)去的,所以還是得后端再防一層
項(xiàng)目實(shí)現(xiàn)思路
項(xiàng)目實(shí)現(xiàn)不太復(fù)雜,但要照顧到的地方很多
- 基本路由
- 動(dòng)態(tài)路由
- 多層嵌套路由
- 多層嵌套動(dòng)態(tài)路由
- meta替代品
- 文件不符合規(guī)則的友好處理
- 命名轉(zhuǎn)換統(tǒng)一
- node中原生fs模塊十分不友好
要考慮的小細(xì)節(jié)還挺多的,特別是當(dāng)路由過(guò)于復(fù)雜的情況
但node的 fs
的坑點(diǎn)是我沒(méi)有想到的,特別是在跨平臺(tái)上,所以我們舍棄了使用原生的 fs
模塊,用 chokidar
和 fs-extra
替代了 fs
的部分功能
前段時(shí)間也在學(xué)習(xí) vue
的ast語(yǔ)法樹(shù),所以學(xué)習(xí)了下思路去嘗試構(gòu)建一棵ast,不過(guò)方法還是有區(qū)別的,vue構(gòu)建語(yǔ)法樹(shù)是通過(guò)正則拆分了 元素開(kāi)始標(biāo)簽
元素屬性
元素字符
元素結(jié)束標(biāo)簽
等然后拼接而成的,拼接的過(guò)程特別復(fù)雜,這個(gè)項(xiàng)目會(huì)簡(jiǎn)單很多,直接通過(guò)文件讀取遞歸遍歷目錄就可以生成一棵 ast
了
然后通過(guò)語(yǔ)法樹(shù)去構(gòu)建字符串的 router.js
,構(gòu)建的過(guò)程還比較麻煩,最后將構(gòu)建好的字符串寫(xiě)入文件就大功告成了
項(xiàng)目還需要完善的地方
單元測(cè)試
現(xiàn)在的單元測(cè)試覆蓋率已經(jīng)100%了,但我覺(jué)得仍然有比較多稍微復(fù)雜的情況沒(méi)有寫(xiě)到,之后會(huì)不僅看單元測(cè)試覆蓋率,而是按想到需要測(cè)試得功能點(diǎn)去補(bǔ)充完整
測(cè)試環(huán)境
項(xiàng)目接入的是 circleci
,沒(méi)法在 windows
下測(cè)試,平常用的開(kāi)發(fā)環(huán)境也是 mac
,所以測(cè)試環(huán)境方面之后還要去研究研究其他可以支持windows的ci工具,并對(duì)不同node版本進(jìn)行測(cè)試
其實(shí)現(xiàn)在在 windows
下也有一個(gè)bug,但我發(fā)現(xiàn) nuxt
也有這個(gè)bug,所以感覺(jué)可能這不是一個(gè)bug或許是一個(gè)feature,之后也會(huì)去提一個(gè) issue
去請(qǐng)教一下,也不知道是不是我電腦的問(wèn)題,簡(jiǎn)單說(shuō)就是 fs.watch
去監(jiān)聽(tīng)文件目錄的時(shí)候(但這里其實(shí)用的是 chokidar
,不過(guò)都一樣)當(dāng)去改變之前已有的文件目錄的名字是改不了的, windows
下會(huì)提示你什么當(dāng)前文件被引用了,需要結(jié)束掉進(jìn)程這個(gè)文件名才能被修改
更友好的支持
項(xiàng)目目前支持的是node版本> 8.15.1,僅支持 webpack4
,之后會(huì)支持 webpack3
和即將到來(lái)的 webpack5
更多的功能
除了剛才提到的一個(gè)簡(jiǎn)單路由的列子和設(shè)置忽略項(xiàng),我們還支持了 vue-router
的其他核心功能,包括 動(dòng)態(tài)路由
嵌套路由
全局路由守衛(wèi)
meta替代品
等其他功能,相關(guān)功能點(diǎn)都寫(xiě)在了我們開(kāi)源倉(cāng)庫(kù)的文檔中,詳細(xì)的用法和注意事項(xiàng),可以訪問(wèn)我們的 github倉(cāng)庫(kù) ,如果覺(jué)得項(xiàng)目還不錯(cuò)的話,可以給我們點(diǎn)一顆小星星,當(dāng)然如果你在使用中發(fā)現(xiàn)了和預(yù)期不太一樣的情況或者bug可以隨時(shí)給我們提 issue
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue 驗(yàn)證碼界面實(shí)現(xiàn)點(diǎn)擊后標(biāo)灰并設(shè)置div按鈕不可點(diǎn)擊狀態(tài)
今天小編就為大家分享一篇vue 驗(yàn)證碼界面實(shí)現(xiàn)點(diǎn)擊后標(biāo)灰并設(shè)置div按鈕不可點(diǎn)擊狀態(tài),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-10-10手把手教你vue-cli單頁(yè)到多頁(yè)應(yīng)用的方法
本篇文章主要介紹了手把手教你vue-cli單頁(yè)到多頁(yè)應(yīng)用的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05vue實(shí)現(xiàn)的上拉加載更多數(shù)據(jù)/分頁(yè)功能示例
這篇文章主要介紹了vue實(shí)現(xiàn)的上拉加載更多數(shù)據(jù)/分頁(yè)功能,涉及基于vue的事件響應(yīng)、數(shù)據(jù)交互等相關(guān)操作技巧,需要的朋友可以參考下2019-05-05Vue2.0中三種常用傳值方式(父?jìng)髯?、子傳父、非父子組件傳值)
在Vue的框架開(kāi)發(fā)的項(xiàng)目過(guò)程中,經(jīng)常會(huì)用到組件來(lái)管理不同的功能,有一些公共的組件會(huì)被提取出來(lái)。下面通過(guò)本文給大家介紹Vue開(kāi)發(fā)中常用的三種傳值方式父?jìng)髯?、子傳父、非父子組件傳值,需要的朋友參考下吧2018-08-08Vue + element實(shí)現(xiàn)動(dòng)態(tài)顯示后臺(tái)數(shù)據(jù)到options的操作方法
最近遇到一個(gè)需求需要實(shí)現(xiàn)selector選擇器中選項(xiàng)值options 數(shù)據(jù)的動(dòng)態(tài)顯示,而非寫(xiě)死的數(shù)據(jù),本文通過(guò)實(shí)例代碼給大家分享實(shí)現(xiàn)方法,感興趣的朋友一起看看吧2021-07-07vue-cli 3.x配置跨域代理的實(shí)現(xiàn)方法
這篇文章主要介紹了vue-cli 3.x配置跨域代理的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04vue解決刷新頁(yè)面時(shí)會(huì)出現(xiàn)變量閃爍的問(wèn)題
這篇文章主要介紹了vue解決刷新頁(yè)面時(shí)會(huì)出現(xiàn)變量閃爍的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01解決antd datepicker 獲取時(shí)間默認(rèn)少8個(gè)小時(shí)的問(wèn)題
這篇文章主要介紹了解決antd datepicker 獲取時(shí)間默認(rèn)少8個(gè)小時(shí)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10