摘要:同時(shí)增加了單元測(cè)試,使用了,增加了可視化配置權(quán)限,增加了自定義布局等等,優(yōu)化了原先的權(quán)限方案,支持不刷新頁(yè)面更新路由等等功能。雖然它的初衷是為了單元測(cè)試的,但正好滿足了我們的需求。它會(huì)重寫瀏覽器的對(duì)象,從而才能攔截所有請(qǐng)求,代理到本地。
前言
vue-element-admin 從 2017.04.17提交第一個(gè) commit 以來(lái),維護(hù)至今已經(jīng)有兩年多的時(shí)間了了,發(fā)布了四十多個(gè)版本,收獲了三萬(wàn)多的 stars,遠(yuǎn)遠(yuǎn)的超出了自己的預(yù)期。距離上次手摸手系列教程也已經(jīng)過去了很久,主要因?yàn)椋鹤鳛橐粋€(gè)個(gè)人開源項(xiàng)目,維持它已經(jīng)很難了,所以真的沒啥時(shí)間寫詳細(xì)的教程了,光是維護(hù)項(xiàng)目 文檔 就讓我很頭疼了。也有不少人建議我出付費(fèi)教學(xué)視頻,但我個(gè)人還是更愿意把這個(gè)時(shí)間投入到維護(hù)開源項(xiàng)目之中吧。
本篇教程主要是趁著vue-element-admin發(fā)布了 v4.0 新版本,首先來(lái)簡(jiǎn)單說一下4.0版本做了哪些改動(dòng)和優(yōu)化。后半部分則會(huì)分享一些新的思考和一些小技巧吧。之前幾篇手摸手文章都差不多兩年前的了,但隨著技術(shù)的不斷發(fā)展迭代,很多之前的不能解決的問題也是都是有了新的解決方案的,同時(shí)也會(huì)出現(xiàn)一些新的問題和挑戰(zhàn)。
4.0 做了什么
首先大概說一下4.0版本做了些什么,通過 pull request 可以看出這是一次比較大的升級(jí),有大概 170 多次的 commits,200 多個(gè)文件的改動(dòng)。其中最大的改變是接軌 vue 社區(qū),直接通過 vue-cli來(lái)進(jìn)行構(gòu)建,省去了很多額外繁瑣的配置(下文會(huì)介紹),并修改了之前 mock 數(shù)據(jù)的方案,本地改用 mock-server 來(lái)解決之前mockjs帶來(lái)的各種問題。同時(shí)增加了 jest 單元測(cè)試,使用了async/await,增加了可視化配置權(quán)限,增加了自定義布局等等,優(yōu)化了原先addRoutes的權(quán)限方案,支持不刷新頁(yè)面更新路由等等功能。具體的可看 github release。接下來(lái)我們著重來(lái)分析一下這幾個(gè)功能。
vue-cli@3
本身配置方面沒有啥特別好說的,官方文檔已經(jīng)寫得很詳細(xì)了。這次更新基本上就是基于 webpack-chain 把之前的 webpack 配置遷移了一遍,因?yàn)?b>vue-cli幫你做了很多默認(rèn)配置,所有可以省去一些代碼。當(dāng)然這種out-of-the-box的工具利弊也很明顯,它能快速上手,大部分簡(jiǎn)單場(chǎng)景無(wú)需任何額外配置基本就能用了。但對(duì)于復(fù)雜度高的或者自定義性強(qiáng)的項(xiàng)目來(lái)說,配置復(fù)雜度可能沒有減少太多。它要求你不僅要對(duì) webpack 或者相關(guān)工程化的東西很很熟悉,你還要對(duì)vue-cli做的一些默認(rèn)配置和參數(shù)也有有一定了解,時(shí)不時(shí)要去看一下源碼它到底干了啥,有的時(shí)候它的一些 plugin 出現(xiàn)了問題還不太好解決。而且說實(shí)話 webpack-chain 的書寫也是有些門檻的,大部分情況下我也很難保證自己的配置寫對(duì)的,還好官方提供了inspec功能,能讓配置簡(jiǎn)單了不少。當(dāng)你想知道自己的 vue-config.js 里的配置到底對(duì)不對(duì)的時(shí)候,你可以在命令行里執(zhí)行vue inspect > output.js,它會(huì)將你最終生成的config展現(xiàn)在output.js之中,不過它默認(rèn)顯示的是開發(fā)環(huán)境的配置。如果你想查看其它環(huán)境的配置可以通過vue inspect --mode production > output.js。在寫構(gòu)建配置的時(shí)候這個(gè)功能很有幫助,同時(shí)也能幫助你了解vue-cli在構(gòu)建時(shí)到底幫你做了些什么。
其它還有些需要注意的如:環(huán)境變量 必須以VUE_APP_開頭啊,怎么設(shè)置polyfill啊,怎么配置各種各樣的loader啊,就不展開了,文檔或者社區(qū)都有很多文章了。具體配置可以參考 vue.config.js
這里還有一個(gè)黑科技,看過我之前文章的小伙伴應(yīng)該還有印象,我一般在開發(fā)環(huán)境是不使用路由懶加載的,因?yàn)檫@樣會(huì)導(dǎo)致熱更新速度變慢,具體的可以看之前的 文章,在vue-cli@3中可以更簡(jiǎn)單的實(shí)現(xiàn),你只要在.env.development環(huán)境變量配置文件中設(shè)置VUE_CLI_BABEL_TRANSPILE_MODULES:true就可以了。它的實(shí)現(xiàn)邏輯和原理與之前還是一樣的,還是基于 plugins babel-plugin-dynamic-import-node 來(lái)實(shí)現(xiàn)的。之所以在vue-cli中只需要設(shè)置一個(gè)變量就可以了,是借用了vue-cli它的默認(rèn)配置,它幫你代碼都寫好了。通過閱讀 源碼 可知,vue-cli會(huì)通過VUE_CLI_BABEL_TRANSPILE_MODULES這個(gè)環(huán)境變量來(lái)區(qū)分是否使用babel-plugin-dynamic-import-node,所以我們只要開其它就可以。雖然它的初衷是為了單元測(cè)試的,但正好滿足了我們的需求。
總的來(lái)說,vue-cli對(duì)于大部分用戶來(lái)說還是省去了一些繁瑣的配置的。如果你使用本項(xiàng)目的話,基本也不需要做其它過多的額外配置的。
redirect 刷新頁(yè)面
在不刷新頁(yè)面的情況下,更新頁(yè)面。這個(gè) issue 兩年前就提出來(lái)了,之前的文章里面也提供了一個(gè) 解決方案。在這里分享一下,我目前使用的新方案。
// 先注冊(cè)一個(gè)名為 `redirect` 的路由
// 手動(dòng)重定向頁(yè)面到 "/redirect" 頁(yè)面 const { fullPath } = this.$route this.$router.replace({ path: "/redirect" + fullPath })
當(dāng)遇到你需要刷新頁(yè)面的情況,你就手動(dòng)重定向頁(yè)面到redirect頁(yè)面,它會(huì)將頁(yè)面重新redirect重定向回來(lái),由于頁(yè)面的 key 發(fā)生了變化,從而間接實(shí)現(xiàn)了刷新頁(yè)面組件的效果。
addRoutes && removeRoutes
看過我之前文章的人肯定知道,我目前 vue 項(xiàng)目的權(quán)限控制都是通過 addRoutes來(lái)實(shí)現(xiàn)的。簡(jiǎn)單說就是:用戶登錄之后會(huì)返回一個(gè)權(quán)限憑證Token,用戶在根據(jù)這個(gè)Token去問服務(wù)端詢問自己的權(quán)限,辟如服務(wù)端返回權(quán)限是["editor"],前端再根據(jù)這個(gè)權(quán)限動(dòng)態(tài)生成他能訪問的路由,再通過addRoutes進(jìn)行動(dòng)態(tài)的路由掛載。具體的代碼可見 permission.js
但這個(gè)方案一直是有一個(gè)弊端的。那就是動(dòng)態(tài)添加的路由,并不能動(dòng)態(tài)的刪除。這就是導(dǎo)致一個(gè)問題,當(dāng)用戶權(quán)限發(fā)生變化的時(shí)候,或者說用戶登出的時(shí)候,我們只能通過刷新頁(yè)面的方式,才能清空我們之前注冊(cè)的路由。之前老版本的 vue-element-admin就一直采用的是這種方式。雖然能用,但作為一個(gè) spa,刷新頁(yè)面其實(shí)是一種很糟糕的用戶體驗(yàn)。但是官方也遲遲沒有出相關(guān)的 remove api,相關(guān) issue
后來(lái)發(fā)現(xiàn)了一種 hack 的方法,能很好的動(dòng)態(tài)清除注冊(cè)的路由。先看代碼:
它的原理其實(shí)很簡(jiǎn)單,所有的 vue-router 注冊(cè)的路由信息都是存放在matcher之中的,所以當(dāng)我們想清空路由的時(shí)候,我們只要新建一個(gè)空的Router實(shí)例,將它的matcher重新賦值給我們之前定義的路由就可以了。巧妙的實(shí)現(xiàn)了動(dòng)態(tài)路由的清除。 現(xiàn)在我們只需要調(diào)用resetRouter,就能得到一個(gè)空的路有實(shí)例,之后你就可以重新addRoutes你想要的路由了。完整的代碼實(shí)例 router.js,resetRouter
Mock 數(shù)據(jù)
如果你在實(shí)際開發(fā)中,最理想的前后端交互方式當(dāng)然是后端先幫我們 mock 數(shù)據(jù),然后前端開發(fā)。但現(xiàn)實(shí)很骨感,總會(huì)因?yàn)榉N種原因,前端需要自己來(lái) mock 假數(shù)據(jù)。尤其是我的幾個(gè)開源項(xiàng)目,都是純前端項(xiàng)目,根本沒有后端服務(wù)。 在之前的文章中也介紹過,vue-element-admin 和 vue-admin-template 使用的是 MockJS 和 easy-mock 這兩個(gè)庫(kù)。但實(shí)際用下來(lái)兩者都有一些問題。
MockJs
它的原理是: 攔截了所有的請(qǐng)求并代理到本地,然后進(jìn)行數(shù)據(jù)模擬,所以你會(huì)發(fā)現(xiàn) network 中沒有發(fā)出任何的請(qǐng)求。但它的最大的問題是就是它的實(shí)現(xiàn)機(jī)制。它會(huì)重寫瀏覽器的XMLHttpRequest對(duì)象,從而才能攔截所有請(qǐng)求,代理到本地。大部分情況下用起來(lái)還是蠻方便的,但就因?yàn)樗貙懥?b>XMLHttpRequest對(duì)象,所以比如progress方法,或者一些底層依賴XMLHttpRequest的庫(kù)都會(huì)和它發(fā)生不兼容,可以看一下我項(xiàng)目的 issues,就知道多少人被坑了。
它還有一個(gè)問題:因?yàn)槭撬潜镜啬M數(shù)據(jù),實(shí)際上不會(huì)走任何網(wǎng)絡(luò)請(qǐng)求。所以本地調(diào)試起來(lái)很蛋疼,只能通過console.log來(lái)調(diào)試。就拿vue-element-admin來(lái)說,想搞清楚 getInfo()接口返回了什么數(shù)據(jù),只能通過看源碼或者手動(dòng) Debug 才能知道。
Easy-Mock
這個(gè)項(xiàng)目剛出的時(shí)候用的人比較少,還真的挺好用的。天然支持跨域,還是支持MockJs的所有語(yǔ)法,我在之前也推薦過。但因?yàn)橛玫娜硕嗔?,它的免費(fèi)服務(wù)會(huì)經(jīng)常的掛,可以說天天掛。。。但畢竟人家這是免費(fèi)的服務(wù),也不能苛求什么,官方的建議是自己搭建服務(wù)。如果你的公司整體搭建一個(gè)這樣的 mock 服務(wù)的話也是一個(gè)不錯(cuò)的選擇。但大部分人可能還是沒有這個(gè)技術(shù)條件的。
所以我一直在尋求一個(gè)更好的解決方案,我也去體驗(yàn)了其它很多 mock api 服務(wù),如 mockapi、Mocky 等等??傊w驗(yàn)都不能滿足我的需求。
在v4.0版本之后,在本地會(huì)啟動(dòng)一個(gè)mock-server來(lái)模擬數(shù)據(jù),線上環(huán)境還是繼續(xù)使用mockjs來(lái)進(jìn)行模擬(因?yàn)楸卷?xiàng)目是一個(gè)純前端項(xiàng)目,你也可以自己搭建一個(gè)線上 server 來(lái)提供數(shù)據(jù))。不管是本地還是線上所以的數(shù)據(jù)模擬都是基于mockjs生成的,所以只要寫一套 mock 數(shù)據(jù),就可以在多環(huán)境中使用。
該方案的好處是,在保留 mockjs的優(yōu)勢(shì)的同時(shí),解決之前的痛點(diǎn)。由于我們的 mock 是完全基于webpack-dev-serve來(lái)實(shí)現(xiàn)的,所以在你啟動(dòng)前端服務(wù)的同時(shí),mock-server就會(huì)自動(dòng)啟動(dòng),這里還通過 chokidar 來(lái)觀察 mock 文件夾內(nèi)容的變化。在發(fā)生變化時(shí)會(huì)清除之前注冊(cè)的mock-api接口,重新動(dòng)態(tài)掛載新的接口,從而支持熱更新。有興趣的可以自己看一下代碼 mock-server.js。由于是一個(gè)真正的server,所以你可以通過控制臺(tái)中的network,清楚的知道接口返回的數(shù)據(jù)結(jié)構(gòu)。并且同時(shí)解決了之前mockjs會(huì)重寫 XMLHttpRequest對(duì)象,導(dǎo)致很多第三方庫(kù)失效的問題。
在本地開發(fā)環(huán)境中基于webpack-dev-serve的 after這個(gè)middleware中間件,在這里自動(dòng)讀取你的 mock文件,模擬出 REST API,它最大的好處是,完全不需要什么額外的工作,完全基于webpack-dev-serve就能實(shí)現(xiàn)。如果你還是想多帶帶啟動(dòng)一個(gè)serve也是可以的,完全可以引入一個(gè)express或者其它插件來(lái)啟動(dòng)一個(gè) mock-serve。
我們模擬數(shù)據(jù)有了,現(xiàn)在要做的事情就是,將我們的接口代理到我們的 mock 服務(wù)上就好了,這里我們使用webpack-dev-serve自帶的 proxy進(jìn)行接口代理。
proxy: { // xxx-api/login => mock/login [process.env.VUE_APP_BASE_API]: { target: `http://localhost:${port}/mock`, changeOrigin: true, pathRewrite: { ["^" + process.env.VUE_APP_BASE_API]: "" } } }
snippets 自動(dòng)生成代碼片段
平時(shí)日常工作中,做最多的就是寫業(yè)務(wù)模塊和組件。當(dāng)每次新開一個(gè)view或者component的時(shí)候都需要手動(dòng)創(chuàng)建一個(gè)新.vue文件,然后再創(chuàng)建、、這些標(biāo)簽,還是有些麻煩的。
所以在新版本中,基于plop,提供了幾個(gè)基礎(chǔ)模板,方便創(chuàng)建新的view或者component。 執(zhí)行如下命令:
npm run new
如上面 gif 所示,現(xiàn)在只要輕松的點(diǎn)幾次回車就可以輕松生成我要的基礎(chǔ)代碼片段。這里只是一個(gè) demo,你完全可以按照自己需求定制模板。老版本的vue-cli實(shí)現(xiàn)邏輯和它類似。
如果你覺得配置太復(fù)雜,我推薦你可以安裝如 Vue 2 Snippets VS Code插件。 這種代碼片段在平時(shí)工作中還是能提升不少開發(fā)效率的。
async/await or promise
本次更新中,我也將部分代碼用了async/await的方式替代了原有的 promise方式,主要是 @/src/permission.js。有興趣的大家自己可以通過 git-history 自己對(duì)比下,可以發(fā)現(xiàn)代碼閱讀性高了不少。 不過本項(xiàng)目中也并沒有把所有promise用async/await替代。我來(lái)簡(jiǎn)單說一下我的看法。
6 個(gè) Async/Await 優(yōu)于 Promise 的方面,這篇文章很多人應(yīng)該都看過,里面大部分觀點(diǎn)我都是同意的,大部分復(fù)雜場(chǎng)景下async/await的確是更優(yōu)解。但相對(duì)的也不是所有的情況下都是async/await寫起來(lái)讓我更爽的。先說說我最不爽的地方是它的錯(cuò)誤處理,try catch讓這個(gè)代碼結(jié)構(gòu)看起來(lái)就很奇怪(當(dāng)然也有很多人很喜歡這種錯(cuò)誤處理形式。社區(qū)也是相對(duì)的解決方案類似go語(yǔ)言的風(fēng)格,比如 await-to-js
[err, res] = await to(getInfo()) if(err) //do something
這個(gè)方案是不錯(cuò),但還需要引入一個(gè)新的庫(kù),增加了學(xué)習(xí)成本,得不償失。所以以我個(gè)人的習(xí)慣,當(dāng)只有一個(gè)異步請(qǐng)求,且需要做錯(cuò)誤處理的情況下,更傾向于使用 promise。比如
// promise getInfo() .then(res => { //do somethings }) .catch(err => { //do somethings }) // async/await try { const res = await getInfo() //do somethings } catch (error) { //do somethings }
在有嵌套請(qǐng)求的情況下,肯定是 async/await 更直觀的。
// promise a(() => { b(() => { c() }) }) // async/await await a() await b() await c()
當(dāng)然代碼寫的好與不好還是取決于寫代碼的人的。比如一個(gè)常見的業(yè)務(wù)場(chǎng)景:有兩個(gè)并發(fā)的異步請(qǐng)求,在都完成后do something。但很多人會(huì)錯(cuò)誤的用串行的方式實(shí)現(xiàn)了。
//錯(cuò)誤 await a() await b() //這樣變成了 a().then(() => b() ) // a 好了才會(huì)執(zhí)行 b done() //正確 await Promise.all([a(), b()]) done()
還有一個(gè)小細(xì)節(jié)async/await打包后的代碼其實(shí)會(huì)比 promise 復(fù)雜很多, 當(dāng)然這個(gè)是一個(gè)忽略不計(jì)得問題。
總結(jié):我認(rèn)為它們兩個(gè)人并不是or的關(guān)系,在特定的業(yè)務(wù)場(chǎng)景下,選擇相對(duì)而言代碼可讀性更好地解決方案。
以上所述純個(gè)人偏愛,并非什么最佳實(shí)現(xiàn)。具體該怎么選擇還是需要大家更具自己團(tuán)隊(duì)的風(fēng)格或者自己的理解來(lái)判斷。
命名規(guī)范
其實(shí)剛開始我寫 vue 文件的時(shí)候也不注意,各種駝峰啊、大寫開頭 (PascalCase)還是橫線連接 (kebab-case)混著來(lái),誰(shuí)叫 vue 都可以,在 風(fēng)格指南 中也沒有定論。不過基于本項(xiàng)目我還是整理了一套文件的命名規(guī)則。
所有的Component文件都是以大寫開頭 (PascalCase),這也是官方所 推薦的。
但除了 index.vue。
例子:
@/src/components/BackToTop/index.vue
@/src/components/Charts/Line.vue
@/src/views/example/components/Button.vue
所有的.js文件都遵循橫線連接 (kebab-case)。
例子:
@/src/utils/open-window.js
@/src/views/svg-icons/require-icons.js
@/src/components/MarkdownEditor/default-options.js
在views文件下,代表路由的.vue文件都使用橫線連接 (kebab-case),代表路由的文件夾也是使用同樣的規(guī)則。
例子:
@/src/views/svg-icons/index.vue
@/src/views/svg-icons/require-icons.js
使用橫線連接 (kebab-case)來(lái)命名views主要是出于以下幾個(gè)考慮。
橫線連接 (kebab-case) 也是官方推薦的命名規(guī)范之一 文檔
views下的.vue文件代表的是一個(gè)路由,所以它需要和component進(jìn)行區(qū)分(component 都是大寫開頭)
頁(yè)面的url 也都是橫線連接的,比如https://www.xxx.admin/export-excel,所以路由對(duì)應(yīng)的view應(yīng)該要保持統(tǒng)一
沒有大小寫敏感問題
CDN
你可以通過執(zhí)行npm run preview -- --report來(lái)分析webpack打包之后的結(jié)果,觀察各個(gè)靜態(tài)資源的大小。你可以發(fā)現(xiàn)占用空間最多的是第三方依賴。如vue、element-ui、ECharts等。
你可以使用 CDN 外鏈的方式引入這些第三方庫(kù),這樣能大大增加構(gòu)建的速度(通過 CDN 引入的資源不會(huì)經(jīng) webpack 打包)。如果你的項(xiàng)目沒有自己的CDN服務(wù)的話,使用一些第三方的CDN服務(wù),如 jsdelivr、unpkg 等是一個(gè)很好的選擇,它提供過了免費(fèi)的資源加速,同時(shí)提供了緩存優(yōu)化,由于你的第三方資源是在html中通過script引入的,它的緩存更新策略都是你自己手動(dòng)來(lái)控制的,省去了你需要優(yōu)化緩存策略功夫。
很多文章說使用 CDN 引入的方式能大大減小代碼的體積,這是不可能的。雖然打包完的 bundle小了,但那部分代碼只是被你拆出去,用CDN的方式引入罷了。你想減小體積,最高效的方案是啟用GZIP。
暫時(shí)構(gòu)建速度還沒有遇到什么瓶頸,所有沒有必要多帶帶剝離部分第三方依賴。使用CDN引入的方式等于一些第三方依賴的版本你是通過package.json來(lái)控制的,一些依賴則需要手動(dòng)維護(hù),增加了一些維護(hù)成本。目前基于 webpack 的optimization.splitChunks已經(jīng)做了資源的緩存優(yōu)化,靜態(tài)資源的緩存已經(jīng)做得很好了。并且目前所有的靜態(tài)資源都會(huì)上傳到自己的CDN服務(wù),沒有必要使用第三方的CDN服務(wù)。
當(dāng)然所有的優(yōu)化都是需要結(jié)合自己的具體業(yè)務(wù)來(lái)調(diào)整的! 之后可能會(huì)采用這種引入方式,或者使用webpack dll的方式進(jìn)行優(yōu)化。如果你覺得CDN引入對(duì)于的項(xiàng)目有益處,你可以遵循如下方法進(jìn)行修改:
先找到 vue.config.js, 添加 externals 讓 webpack 不打包 vue 和 element
externals: { vue: "Vue", "element-ui":"ELEMENT" }
然后配置那些第三方資源的CDN,請(qǐng)注意先后順序。
const cdn = { css: [ // element-ui css "https://unpkg.com/element-ui/lib/theme-chalk/index.css" ], js: [ // vue must at first! "https://unpkg.com/vue/dist/vue.js", // element-ui js "https://unpkg.com/element-ui/lib/index.js" ] }
之后通過 html-webpack-plugin注入到 index.html之中:
config.plugin("html").tap(args => { args[0].cdn = cdn return args })
找到 public/index.html。通過你配置的CND Config 依次注入 css 和 js。
<% for(var css of htmlWebpackPlugin.options.cdn.css) { %> <% } %> <% for(var js of htmlWebpackPlugin.options.cdn.js) { %> <% } %>
完整的 代碼修改
最終你可以使用 npm run preview -- --report 查看效果 如圖:
同理,其它第三方依賴都可以使用相同的方式處理(比如vuex、vue-router等)。當(dāng)然你也可以選擇使用 DLLPlugin的方式來(lái)處理第三方依賴,從而來(lái)優(yōu)化構(gòu)建。
小技巧與建議
Watch immediate
這個(gè)已經(jīng)算是一個(gè)比較常見的技巧了,這里就簡(jiǎn)單說一下。當(dāng) watch 一個(gè)變量的時(shí)候,初始化時(shí)并不會(huì)執(zhí)行,如下面的例子,你需要在created的時(shí)候手動(dòng)調(diào)用一次。
// bad created() { this.fetchUserList(); }, watch: { searchText: "fetchUserList", }
你可以添加immediate屬性,這樣初始化的時(shí)候也會(huì)觸發(fā),然后上面的代碼就能簡(jiǎn)化為:
// good watch: { searchText: { handler: "fetchUserList", immediate: true, } }
ps: watch 還有一個(gè)容易被大家忽略的屬性deep。當(dāng)設(shè)置為true時(shí),它會(huì)進(jìn)行深度監(jiān)聽。簡(jiǎn)而言之就是你有一個(gè) const obj={a:1,b:2},里面任意一個(gè) key 的 value 發(fā)生變化的時(shí)候都會(huì)觸發(fā)watch。應(yīng)用場(chǎng)景:比如我有一個(gè)列表,它有一堆query篩選項(xiàng),這時(shí)候你就能deep watch它,只有任何一個(gè)篩序項(xiàng)改變的時(shí)候,就自動(dòng)請(qǐng)求新的數(shù)據(jù)?;蛘吣憧梢?b>deep watch一個(gè) form 表單,當(dāng)任何一個(gè)字段內(nèi)容發(fā)生變化的時(shí)候,你就幫它做自動(dòng)保存等等。
Attrs 和 Listeners
這兩個(gè)屬性是 vue 2.4 版本之后提供的,它簡(jiǎn)直是二次封裝組件或者說寫高階組件的神器。在我們平時(shí)寫業(yè)務(wù)的時(shí)候免不了需要對(duì)一些第三方組件進(jìn)行二次封裝。比如我們需要基于el-select分裝一個(gè)帶有業(yè)務(wù)特性的組件,根據(jù)輸入的 name 搜索用戶,并將一些業(yè)務(wù)邏輯分裝在其中。但el-select這個(gè)第三方組件支持幾十個(gè)配置參數(shù),我們當(dāng)然可以適當(dāng)?shù)奶暨x幾個(gè)參數(shù)通過 props 來(lái)傳遞,但萬(wàn)一哪天別人用你的業(yè)務(wù)組件的時(shí)候覺得你的參數(shù)少了,那你只能改你封裝的組件了,亦或是哪天第三方組件加入了新參數(shù),你該怎么辦?
其實(shí)我們的這個(gè)組件只是基于el-select做了一些業(yè)務(wù)的封裝,比如添加了默認(rèn)的placeholder,封裝了遠(yuǎn)程 ajax 搜索請(qǐng)求等等,總的來(lái)說它就是一個(gè)中間人組件,只負(fù)責(zé)傳遞數(shù)據(jù)而已。
這時(shí)候我們就可以使用v-bind="$attrs":傳遞所有屬性、v-on="$listeners"傳遞所有方法。如下圖所示:
這樣,我們沒有在$props中聲明的方法和屬性,會(huì)通過$attrs、$listeners直接傳遞下去。這兩個(gè)屬性在我們平時(shí)分裝第三方組件的時(shí)候非常有用!
.sync
這個(gè)也是 vue 2.3 之后新加的一個(gè)語(yǔ)法糖。這也是平時(shí)在分裝組件的時(shí)候很好用的一個(gè)語(yǔ)法糖,它的實(shí)現(xiàn)機(jī)制和v-model是一樣的。
當(dāng)你有需要在子組件修改父組件值的時(shí)候這個(gè)方法很好用。 線上例子
Computed 的 get 和 set
computed 大家肯定都用過,它除了可以緩存計(jì)算屬性外,它在處理傳入數(shù)據(jù)和目標(biāo)數(shù)據(jù)格式不一致的時(shí)候也是很有用的。set、get 文檔
上面說的可能還是是有點(diǎn)抽象,舉一個(gè)簡(jiǎn)單的的例子:我們有一個(gè) form 表單,from 里面有一個(gè)記錄創(chuàng)建時(shí)間的字段create_at。我們知道前端的時(shí)間戳都是 13 位的,但很多后端默認(rèn)時(shí)間戳是 10 位的,這就很蛋疼了。前端和后端的時(shí)間戳位數(shù)不一致。最常見的做法如下:
上面的代碼主要做的是:在拿到數(shù)據(jù)的時(shí)候?qū)⒑蠖?10 位時(shí)間戳轉(zhuǎn)化為 13 位時(shí)間戳,之后再向服務(wù)端發(fā)送數(shù)據(jù)的時(shí)候再轉(zhuǎn)化回 10 位時(shí)間戳傳給后端。目前這種做法當(dāng)然是可行的,但之后可能不僅只有創(chuàng)建接口,還有更新接口的時(shí)候,你還需要在update的接口里在做一遍同樣數(shù)據(jù)轉(zhuǎn)化的操作么?而且這只是一個(gè)最簡(jiǎn)單的例子,真實(shí)的 form 表單會(huì)復(fù)雜的多,需要處理的數(shù)據(jù)也更為的多。這時(shí)候代碼就會(huì)變得很難維護(hù)。
這時(shí)候就可以使用 computed 的 set 和 get 方法了。
通過上面的代碼可以看到,我們把需要做前后端兼容的數(shù)據(jù),放在了 computed 中,從 getData和submit中隔離了數(shù)據(jù)處理的部分。
當(dāng)然上面說的方案還不是最好的方案,你其實(shí)應(yīng)該利用之前所說的v-bind="$attrs"和v-on="$listeners"對(duì)時(shí)間選擇器組件進(jìn)行二次封裝。例如這樣
set 和 get 處理可以做上面說的進(jìn)行一些數(shù)據(jù)處理之外,你也可以把它當(dāng)做一個(gè) watch的升級(jí)版。它可以監(jiān)聽數(shù)據(jù)的變化,當(dāng)發(fā)生變化時(shí),做一些額外的操作。最經(jīng)典的用法就是v-model上綁定一個(gè) vuex 值的時(shí)候,input 發(fā)生變化時(shí),通過 commit更新存在 vuex 里面的值。
具體的解釋你也可以見官方 文檔
Object.freeze
這算是一個(gè)性能優(yōu)化的小技巧吧。在我們遇到一些 big data的業(yè)務(wù)場(chǎng)景,它就很有用了。尤其是做管理后臺(tái)的時(shí)候,經(jīng)常會(huì)有一些超大數(shù)據(jù)量的 table,或者一個(gè)含有 n 多數(shù)據(jù)的圖表,這種數(shù)據(jù)量很大的東西使用起來(lái)最明顯的感受就是卡。但其實(shí)很多時(shí)候其實(shí)這些數(shù)據(jù)其實(shí)并不需要響應(yīng)式變化,這時(shí)候你就可以使用 Object.freeze 方法了,它可以凍結(jié)一個(gè)對(duì)象(注意它不并是 vue 特有的 api)。
當(dāng)你把一個(gè)普通的 JavaScript 對(duì)象傳給 Vue 實(shí)例的 data 選項(xiàng),Vue 將遍歷此對(duì)象所有的屬性,并使用 Object.defineProperty 把這些屬性全部轉(zhuǎn)為 getter/setter,它們讓 Vue 能進(jìn)行追蹤依賴,在屬性被訪問和修改時(shí)通知變化。 使用了 Object.freeze 之后,不僅可以減少 observer 的開銷,還能減少不少內(nèi)存開銷。相關(guān) issue。
使用方式:this.item = Object.freeze(Object.assign({}, this.item))
這里我提供了一個(gè)在線測(cè)速 demo,點(diǎn)我。
通過測(cè)速可以發(fā)現(xiàn)正常情況下1000 x 10 rerender 都穩(wěn)定在 1000ms-2000ms 之間,而開啟了Object.freeze的情況下,rerender 都穩(wěn)住在 100ms-200ms 之間。有接近 10 倍的差距。所以能確定不需要變化檢測(cè)的情況下,big data 還是要優(yōu)化一下的。
Functional
函數(shù)式組件 這個(gè)是文檔里就寫的內(nèi)容,但在其實(shí)很少人會(huì)刻意的去使用。因?yàn)槟悴挥盟?,代碼也不會(huì)有任何問題,用了到可能會(huì)出現(xiàn) bug。
我們先看一個(gè)例子:點(diǎn)我測(cè)試性能 肉眼可見的性能差距。當(dāng)然很多人會(huì)覺得我的項(xiàng)目中也沒有這種變化量級(jí),但我覺得這是一個(gè)程序員的自我修養(yǎng)問題吧。,比如能用v-show的地方就不要用v-if,善用keep-alive和v-once,Object.freeze()處理 vue big data 問題等。雖然都是一些小細(xì)節(jié),但對(duì)性能和體驗(yàn)都是有不少的提升的。更多的性能優(yōu)化技巧請(qǐng)查看該文章 vue-9-perf-secrets
減少全局操作
這其實(shí)并不只是針對(duì) vue 項(xiàng)目的一個(gè)建議,我們平時(shí)寫代碼的時(shí)候一定要盡量避免一些全局的操作。如果必須要用到的時(shí)候,一定要自己檢查,會(huì)不會(huì)產(chǎn)生一些全局的污染或者副作用。
舉幾個(gè)簡(jiǎn)單例子:
我們現(xiàn)在雖然用 vue 寫代碼了,核心思想轉(zhuǎn)變?yōu)橛脭?shù)據(jù)驅(qū)動(dòng) view,不用像jQuery時(shí)代那樣,頻繁的操作 DOM 節(jié)點(diǎn)。但還是免不了有些場(chǎng)景還是要操作 DOM 的。我們?cè)诮M件內(nèi)選擇節(jié)點(diǎn)的時(shí)候一定要切記避免使用 document.querySelector()等一系列的全局選擇器。你應(yīng)該使用this.$el或者this.refs.xxx.$el的方式來(lái)選擇 DOM。這樣就能將你的操作局限在當(dāng)前的組件內(nèi),能避免很多問題。
我們經(jīng)常會(huì)不可避免的需要注冊(cè)一些全局性的事件,比如監(jiān)聽頁(yè)面窗口的變化window.addEventListener("resize", this.__resizeHandler),但再聲明了之后一定要在 beforeDestroy或者destroyed生命周期注銷它。window.removeEventListener("resize", this.__resizeHandler)避免造成不必要的消耗。
避免過多的全局狀態(tài),不是所有的狀態(tài)都需要存在 vuex 中的,應(yīng)該根據(jù)業(yè)務(wù)進(jìn)行合理的進(jìn)行取舍。如果不可避免有很多的值需要存在 vuex 中,建議使用動(dòng)態(tài)注冊(cè)的方式。相關(guān)文檔。只是部分業(yè)務(wù)需要的狀態(tài)處理,建議使用 Event Bus或者使用 簡(jiǎn)單的 store 模式。
css 也應(yīng)該盡量避免寫太多的全局性的樣式。除了一些全局公用的樣式外,所以針對(duì)業(yè)務(wù)的或者組件的樣式都應(yīng)該使用命名空間的方式或者直接使用 vue-loader 提供的 scoped寫法,避免一些全局沖突。文檔
Sass 和 Js 之間變量共享
這個(gè)需求可能有些人沒有遇到過,舉個(gè)實(shí)際例子來(lái)說明一下。
如上面要實(shí)現(xiàn)一個(gè)動(dòng)態(tài)的換膚,就需要將用戶選擇的 theme 主題色傳遞給 css。但同時(shí)初始化的時(shí)候 css 又需要將一個(gè)默認(rèn)主題色傳遞給 js。所以下面我們就分兩塊來(lái)講解。
js 將變量傳遞給 sass 這部分是相對(duì)簡(jiǎn)單就可以實(shí)現(xiàn)的,實(shí)現(xiàn)方案也很多。最簡(jiǎn)單的方法就是通過 在模板里面寫 style 標(biāo)簽來(lái)實(shí)現(xiàn),就是俗話所說的內(nèi)聯(lián)標(biāo)簽。
或者使用 css var(),在線 demo,還有用 less 的話modifyVars,等等方案都能實(shí)現(xiàn) js 與 css 的變量傳遞。
sass 將變量給 js
還是那前面那個(gè)換膚來(lái)舉例子,我們頁(yè)面初始化的時(shí)候,總需要一個(gè)默認(rèn)主題色吧,假設(shè)我們?cè)?var.scss中聲明了一個(gè) theme:blue,我們?cè)?js 中該怎么獲取這個(gè)變量呢?我們可以通過 css-modules :export來(lái)實(shí)現(xiàn)。更具體的解釋-How to Share Variables Between Javascript and Sass
// var.scss $theme: blue; :export { theme: $theme; }
// test.js import variables from "@/styles/var.scss" console.log(variables.theme) // blue
當(dāng) js 和 css 共享一個(gè)變量的時(shí)候這個(gè)方案還是很實(shí)用的。vue-element-admin 中的側(cè)邊欄的寬度,顏色等等變量都是通過這種方案來(lái)實(shí)現(xiàn)共享的。
其它換膚方案可以參考 聊一聊前端換膚。
自動(dòng)注冊(cè)全局組件
我的業(yè)務(wù)場(chǎng)景大部分是中后臺(tái),雖然封裝和使用了很多第三方組件,但還是免不了需要自己封裝和使用很多業(yè)務(wù)組件。但每次用的時(shí)候還需要手動(dòng)引入,真的是有些麻煩的。
我們其實(shí)可以基于 webpack 的require.context來(lái)實(shí)現(xiàn)自動(dòng)加載組件并注冊(cè)的全局的功能。相關(guān)原理在之前的文章中已經(jīng)闡述過了。具體代碼如下
我們可以創(chuàng)建一個(gè)GlobalComponents文件夾,將你想要注冊(cè)到全局的組件都放在這個(gè)文件夾里,在index.js里面放上如上代碼。之后只要在入口文件main.js中引入即可。
//main.js import "./components/Table/index" // 自動(dòng)注冊(cè)全局業(yè)務(wù)組件
這樣我們可以在模板中直接使用這些全局組建了。不需要再繁瑣的手動(dòng)引入了。
當(dāng)然你也不要為了省事,啥組件都往全局注冊(cè),這樣會(huì)讓你初始化頁(yè)面的時(shí)候你的初始init bundle很大。你應(yīng)該就注冊(cè)那些你經(jīng)常使用且體積不大的組件。那些體積大的組件,如編輯器或者圖表組件還是按需加載比較合理。而且你最好聲明這些全局組件的時(shí)候有一個(gè)統(tǒng)一的命名規(guī)范比如:globel-user-select這樣的,指定一個(gè)團(tuán)隊(duì)規(guī)范,不然人家看到你這個(gè)全局組件會(huì)一臉懵逼,這個(gè)組件是哪來(lái)的。
Lint
這又是一個(gè)老生常談的問題了 vue 的一些最佳實(shí)踐什么的話,這里不討論了,我覺得看官方的 風(fēng)格指南 差不多就夠了。比如避免避免 v-if 和 v-for 用在一起、元素特性的順序這些等等規(guī)則,幾十條規(guī)則,說真的寫了這么久 vue,我也只能記住一些常規(guī)的。什么屬性的順序啊,不太可能記住的。這種東西還是交給程序來(lái)自動(dòng)優(yōu)化才是更合理的選擇。強(qiáng)烈推薦配置編輯器自動(dòng)化處理。具體配置見 文檔。同時(shí)建議結(jié)合 Git Hooks 配合在每次提交代碼時(shí)對(duì)代碼進(jìn)行 lint 校驗(yàn),確保所有提交到遠(yuǎn)程倉(cāng)庫(kù)的代碼都符合團(tuán)隊(duì)的規(guī)范。它主要使用到的工具是husky和lint-staged,詳細(xì)文檔見 Git Hooks
Hook
這個(gè)是一個(gè)文檔里沒有寫的 api,但我覺得是一個(gè)很有用的 api。比如我們平時(shí)使用一些第三方組件,或者注冊(cè)一些全局事件的時(shí)候,都需要在mounted中聲明,在destroyed中銷毀。但由于這個(gè)是寫在兩個(gè)生命周期內(nèi)的,很容易忘記,而且大部分在創(chuàng)建階段聲明的內(nèi)容都會(huì)有副作用,如果你在組件摧毀階段忘記移除的話,會(huì)造成內(nèi)存的泄漏,而且都不太容易發(fā)現(xiàn)。如下代碼:
react 在新版本中也加入了useEffect,將以前的多個(gè) life-cycles 合并、重組,使邏輯更加清晰,這里就不展開了。那 vue 是不是也可以這樣做?我去了看了一下官方的 vue-hooks的 源碼 發(fā)現(xiàn)了一個(gè)新的 api:$on("hook:xxx")。有了它,我們就能將之前的代碼用更簡(jiǎn)單和清楚地方式實(shí)現(xiàn)了。
和 react 的useEffect有異曲同工之妙。
而且我們有了這個(gè) api 之后,能干的事情還不止這個(gè)。有時(shí)候我們會(huì)用一些第三方組件,比如我們有一個(gè)編輯器組件(加載比較慢,會(huì)有白屏),所以我們?cè)谒秩就瓿芍靶枰o它一個(gè)占位符,但可能這個(gè)組件并沒有暴露給我們這個(gè)接口,當(dāng)然我們需要修改這個(gè)組件,在它創(chuàng)建的時(shí)候手動(dòng) emit 一個(gè)事件出去,然后在組件上監(jiān)聽它,比如:
當(dāng)然這也是可行的,但萬(wàn)一還要監(jiān)聽一個(gè)更新或者摧毀的生命周期呢?其實(shí)利用 hook可以很方便的實(shí)現(xiàn)這個(gè)效果。
當(dāng)然在 vue 3.0 版本中可能會(huì)有新的寫法,就不如下面的討論: Dynamic Lifecycle Injection。有興趣的可以自行去研究,這里就不展開了。當(dāng) 3.0 正式發(fā)布之后再來(lái)討論吧。
RoadMap
最后來(lái)說一下,之后需要做的事情吧:
更好的多級(jí)頁(yè)面緩存:目前頁(yè)面的緩存基于keep-alive,但當(dāng)三級(jí)路由嵌套的情況下,支持的并不好。之后探索一個(gè)更好的解決方案。
單元測(cè)試:當(dāng)項(xiàng)目大了之后,沒有單元測(cè)試維護(hù)起來(lái)還是有些吃力的。 之后會(huì)慢慢補(bǔ)上unit-test 的測(cè)試用例。 酌情加上一些e2e-test的例子。
去國(guó)際化:其實(shí)大部分人是不需要國(guó)際化的,默認(rèn)情況下移除國(guó)際化。多帶帶開一個(gè)國(guó)際化分支(v4.1 已完成)。
適配 webpack5:webpack5 還是解決了不少之前的痛點(diǎn)的,正式版發(fā)布之后會(huì)進(jìn)行升級(jí)。
vue 3.0: 等官方發(fā)布之后會(huì)基于新版本進(jìn)行重構(gòu)(這個(gè)或許還有很久)
適配 element-ui 3.0 之前官方發(fā)了 3.0 的打算(我也不知道會(huì)不會(huì)跳票)
總結(jié)
開源不易,且行且珍惜吧。
系列文章:
手摸手,帶你用 vue 擼后臺(tái) 系列一(基礎(chǔ)篇)
手摸手,帶你用 vue 擼后臺(tái) 系列二(登錄權(quán)限篇)
手摸手,帶你用 vue 擼后臺(tái) 系列三 (實(shí)戰(zhàn)篇)
手摸手,帶你用 vue 擼后臺(tái) 系列四(vueAdmin 一個(gè)極簡(jiǎn)的后臺(tái)基礎(chǔ)模板)
手摸手,帶你用 vue 擼后臺(tái) 系列五(v4.0 新版本)
手摸手,帶你封裝一個(gè) vue component
手摸手,帶你優(yōu)雅的使用 icon
手摸手,帶你用合理的姿勢(shì)使用 webpack4(上)
手摸手,帶你用合理的姿勢(shì)使用 webpack4(下)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/6681.html
摘要:同時(shí)增加了單元測(cè)試,使用了,增加了可視化配置權(quán)限,增加了自定義布局等等,優(yōu)化了原先的權(quán)限方案,支持不刷新頁(yè)面更新路由等等功能。雖然它的初衷是為了單元測(cè)試的,但正好滿足了我們的需求。它會(huì)重寫瀏覽器的對(duì)象,從而才能攔截所有請(qǐng)求,代理到本地。 前言 vue-element-admin 從 2017.04.17提交第一個(gè) commit 以來(lái),維護(hù)至今已經(jīng)有兩年多的時(shí)間了了,發(fā)布了四十多個(gè)版本,...
摘要:靈活性和針對(duì)性。所以我覺得大部分組件還是自己封裝來(lái)的更為方便和靈活一些。動(dòng)手開干接下來(lái)我們一起手摸手教改造包裝一個(gè)插件,只要幾分鐘就可以封裝一個(gè)專屬于你的。 項(xiàng)目地址:vue-countTo配套完整后臺(tái)demo地址:vue-element-admin系類文章一:手摸手,帶你用vue擼后臺(tái) 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺(tái) 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶...
摘要:只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙只有動(dòng)手,你才能真正掌握一門技術(shù)持續(xù)更新中項(xiàng)目地址求求求源碼系列跟一起學(xué)如何寫函數(shù)庫(kù)中高級(jí)前端面試手寫代碼無(wú)敵秘籍如何用不到行代碼寫一款屬于自己的類庫(kù)原理講解實(shí)現(xiàn)一個(gè)對(duì)象遵循規(guī)范實(shí)戰(zhàn)手摸手,帶你用擼 Do it yourself!!! 只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙 只有動(dòng)手,你才能真正掌握一門技術(shù) 持續(xù)更新中…… 項(xiàng)目地址 https...
摘要:詳細(xì)具體的使用可以見文章手摸手,帶你優(yōu)雅的使用。為了加速線上鏡像構(gòu)建的速度,我們利用源進(jìn)行加速并且將一些常見的依賴打入了基礎(chǔ)鏡像,避免每次都需要重新下載。 完整項(xiàng)目地址:vue-element-admin系類文章二:手摸手,帶你用vue擼后臺(tái) 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺(tái) 系列三(實(shí)戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺(tái) 系列四(vueAdmin 一...
摘要:社區(qū)的認(rèn)可目前已經(jīng)是相關(guān)最多的開源項(xiàng)目了,體現(xiàn)出了社區(qū)對(duì)其的認(rèn)可。監(jiān)聽事件手動(dòng)維護(hù)列表這樣我們就簡(jiǎn)單的完成了拖拽排序。 完整項(xiàng)目地址:vue-element-admin 系類文章一:手摸手,帶你用vue擼后臺(tái) 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺(tái) 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺(tái) 系列三(實(shí)戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺(tái) 系列...
閱讀 3834·2021-11-24 09:39
閱讀 3752·2021-11-22 12:07
閱讀 1105·2021-11-04 16:10
閱讀 798·2021-09-07 09:59
閱讀 1902·2019-08-30 15:55
閱讀 934·2019-08-30 15:54
閱讀 724·2019-08-29 14:06
閱讀 2473·2019-08-27 10:54