手摸手,帶你用vue擼後臺 系列五(v4.0新版本)

前言

vue-element-admin2017.04.17提交第一個 commit 以來,維護至今已經有兩年多的時間了了,發佈了四十多個版本,收穫了三萬多的 stars,遠遠的超出了本身的預期。距離上次手摸手系列教程也已通過去了好久,主要由於:做爲一個我的開源項目,維持它已經很難了,因此真的沒啥時間寫詳細的教程了,光是維護項目 文檔 就讓我很頭疼了。也有很多人建議我出付費教學視頻,但我我的仍是更願意把這個時間投入到維護開源項目之中吧。javascript

本篇教程主要是趁着vue-element-admin發佈了 v4.0 新版本,首先來簡單說一下4.0版本作了哪些改動和優化。後半部分則會分享一些新的思考和一些小技巧吧。以前幾篇手摸手文章都差很少兩年前的了,但隨着技術的不斷髮展迭代,不少以前的不能解決的問題也是都是有了新的解決方案的,同時也會出現一些新的問題和挑戰。css

4.0 作了什麼

首先大概說一下4.0版本作了些什麼,經過 pull request 能夠看出這是一次比較大的升級,有大概 170 屢次的 commits,200 多個文件的改動。其中最大的改變是接軌 vue 社區,直接經過 vue-cli來進行構建,省去了不少額外繁瑣的配置(下文會介紹),並修改了以前 mock 數據的方案,本地改用 mock-server 來解決以前mockjs帶來的各類問題。同時增長了 jest 單元測試,使用了async/await,增長了可視化配置權限,增長了自定義佈局等等,優化了原先addRoutes的權限方案,支持不刷新頁面更新路由等等功能。具體的可看 github release。接下來咱們着重來分析一下這幾個功能。html

vue-cli@3

自己配置方面沒有啥特別好說的,官方文檔已經寫得很詳細了。此次更新基本上就是基於 webpack-chain 把以前的 webpack 配置遷移了一遍,由於vue-cli幫你作了不少默認配置,全部能夠省去一些代碼。固然這種out-of-the-box的工具利弊也很明顯,它能快速上手,大部分簡單場景無需任何額外配置基本就能用了。但對於複雜度高的或者自定義性強的項目來講,配置複雜度可能沒有減小太多。它要求你不只要對 webpack 或者相關工程化的東西很很熟悉,你還要對vue-cli作的一些默認配置和參數也有有必定了解,時不時要去看一下源碼它到底幹了啥,有的時候它的一些 plugin 出現了問題還不太好解決。並且說實話 webpack-chain 的書寫也是有些門檻的,大部分狀況下我也很難保證本身的配置寫對的,還好官方提供了inspec功能,能讓配置簡單了很多。當你想知道本身的 vue-config.js 裏的配置到底對不對的時候,你能夠在命令行裏執行vue inspect > output.js,它會將你最終生成的config展示在output.js之中,不過它默認顯示的是開發環境的配置。若是你想查看其它環境的配置能夠經過vue inspect --mode production > output.js。在寫構建配置的時候這個功能頗有幫助,同時也能幫助你瞭解vue-cli在構建時到底幫你作了些什麼。前端

其它還有些須要注意的如:環境變量 必須以VUE_APP_開頭啊,怎麼設置polyfill啊,怎麼配置各類各樣的loader啊,就不展開了,文檔或者社區都有不少文章了。具體配置能夠參考 vue.config.jsvue

這裏還有一個黑科技,看過我以前文章的小夥伴應該還有印象,我通常在開發環境是不使用路由懶加載的,由於這樣會致使熱更新速度變慢,具體的能夠看以前的 文章,在vue-cli@3中能夠更簡單的實現,你只要在.env.development環境變量配置文件中設置VUE_CLI_BABEL_TRANSPILE_MODULES:true就能夠了。它的實現邏輯和原理與以前仍是同樣的,仍是基於 plugins babel-plugin-dynamic-import-node 來實現的。之因此在vue-cli中只須要設置一個變量就能夠了,是借用了vue-cli它的默認配置,它幫你代碼都寫好了。經過閱讀 源碼 可知,vue-cli會經過VUE_CLI_BABEL_TRANSPILE_MODULES這個環境變量來區分是否使用babel-plugin-dynamic-import-node,因此咱們只要開其它就能夠。雖然它的初衷是爲了單元測試的,但正好知足了咱們的需求。java

總的來講,vue-cli對於大部分用戶來講仍是省去了一些繁瑣的配置的。若是你使用本項目的話,基本也不須要作其它過多的額外配置的。node

redirect 刷新頁面

在不刷新頁面的狀況下,更新頁面。這個 issue 兩年前就提出來了,以前的文章裏面也提供了一個 解決方案。在這裏分享一下,我目前使用的新方案。react

// 先註冊一個名爲 `redirect` 的路由
<script> export default { beforeCreate() { const { params, query } = this.$route const { path } = params this.$router.replace({ path: '/' + path, query }) }, render: function(h) { return h() // avoid warning message } } </script>
複製代碼
// 手動重定向頁面到 '/redirect' 頁面
const { fullPath } = this.$route
this.$router.replace({
  path: '/redirect' + fullPath
})
複製代碼

當遇到你須要刷新頁面的狀況,你就手動重定向頁面到redirect頁面,它會將頁面從新redirect重定向回來,因爲頁面的 key 發生了變化,從而間接實現了刷新頁面組件的效果。webpack

addRoutes && removeRoutes

看過我以前文章的人確定知道,我目前 vue 項目的權限控制都是經過 addRoutes來實現的。簡單說就是:用戶登陸以後會返回一個權限憑證Token,用戶在根據這個Token去問服務端詢問本身的權限,闢如服務端返回權限是['editor'],前端再根據這個權限動態生成他能訪問的路由,再經過addRoutes進行動態的路由掛載。具體的代碼可見 permission.jsgit

但這個方案一直是有一個弊端的。那就是動態添加的路由,並不能動態的刪除。這就是致使一個問題,當用戶權限發生變化的時候,或者說用戶登出的時候,咱們只能經過刷新頁面的方式,才能清空咱們以前註冊的路由。以前老版本的 vue-element-admin就一直採用的是這種方式。雖然能用,但做爲一個 spa,刷新頁面實際上是一種很糟糕的用戶體驗。可是官方也遲遲沒有出相關的 remove api,相關 issue

後來發現了一種 hack 的方法,能很好的動態清除註冊的路由。先看代碼:

它的原理其實很簡單,全部的 vue-router 註冊的路由信息都是存放在matcher之中的,因此當咱們想清空路由的時候,咱們只要新建一個空的Router實例,將它的matcher從新賦值給咱們以前定義的路由就能夠了。巧妙的實現了動態路由的清除。 如今咱們只須要調用resetRouter,就能獲得一個空的路有實例,以後你就能夠從新addRoutes你想要的路由了。完整的代碼實例 router.jsresetRouter

Mock 數據

若是你在實際開發中,最理想的先後端交互方式固然是後端先幫咱們 mock 數據,而後前端開發。但現實很骨感,總會由於種種緣由,前端須要本身來 mock 假數據。尤爲是個人幾個開源項目,都是純前端項目,根本沒有後端服務。 在以前的文章中也介紹過,vue-element-adminvue-admin-template 使用的是 MockJSeasy-mock 這兩個庫。但實際用下來二者都有一些問題。

  • MockJs

    它的原理是: 攔截了全部的請求並代理到本地,而後進行數據模擬,因此你會發現 network 中沒有發出任何的請求。但它的最大的問題是就是它的實現機制。它會重寫瀏覽器的XMLHttpRequest對象,從而才能攔截全部請求,代理到本地。大部分狀況下用起來仍是蠻方便的,但就由於它重寫了XMLHttpRequest對象,因此好比progress方法,或者一些底層依賴XMLHttpRequest的庫都會和它發生不兼容,能夠看一下我項目的 issues,就知道多少人被坑了。

    它還有一個問題:由於是它是本地模擬數據,實際上不會走任何網絡請求。因此本地調試起來很蛋疼,只能經過console.log來調試。就拿vue-element-admin來講,想搞清楚 getInfo()接口返回了什麼數據,只能經過看源碼或者手動 Debug 才能知道。

  • Easy-Mock

    這個項目剛出的時候用的人比較少,還真的挺好用的。自然支持跨域,仍是支持MockJs的全部語法,我在以前也推薦過。但由於用的人多了,它的免費服務會常常的掛,能夠說每天掛。。。但畢竟人家這是免費的服務,也不能苛求什麼,官方的建議是本身搭建服務。若是你的公司總體搭建一個這樣的 mock 服務的話也是一個不錯的選擇。但大部分人可能仍是沒有這個技術條件的。

新方案

因此我一直在尋求一個更好的解決方案,我也去體驗了其它不少 mock api 服務,如 mockapiMocky 等等。總之體驗都不能知足個人需求。

v4.0版本以後,在本地會啓動一個mock-server來模擬數據,線上環境仍是繼續使用mockjs來進行模擬(由於本項目是一個純前端項目,你也能夠本身搭建一個線上 server 來提供數據)。無論是本地仍是線上因此的數據模擬都是基於mockjs生成的,因此只要寫一套 mock 數據,就能夠在多環境中使用。

該方案的好處是,在保留 mockjs的優點的同時,解決以前的痛點。因爲咱們的 mock 是徹底基於webpack-dev-serve來實現的,因此在你啓動前端服務的同時,mock-server就會自動啓動,這裏還經過 chokidar 來觀察 mock 文件夾內容的變化。在發生變化時會清除以前註冊的mock-api接口,從新動態掛載新的接口,從而支持熱更新。有興趣的能夠本身看一下代碼 mock-server.js。因爲是一個真正的server,因此你能夠經過控制檯中的network,清楚的知道接口返回的數據結構。而且同時解決了以前mockjs會重寫 XMLHttpRequest對象,致使不少第三方庫失效的問題。

在本地開發環境中基於webpack-dev-serveafter這個middleware中間件,在這裏自動讀取你的 mock文件,模擬出 REST API,它最大的好處是,徹底不須要什麼額外的工做,徹底基於webpack-dev-serve就能實現。若是你仍是想單獨啓動一個serve也是能夠的,徹底能夠引入一個express或者其它插件來啓動一個 mock-serve。

咱們模擬數據有了,如今要作的事情就是,將咱們的接口代理到咱們的 mock 服務上就行了,這裏咱們使用webpack-dev-serve自帶的 proxy進行接口代理。

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 自動生成代碼片斷

平時平常工做中,作最多的就是寫業務模塊和組件。當每次新開一個view或者component的時候都須要手動建立一個新.vue文件,而後再建立<template><script><style>這些標籤,仍是有些麻煩的。

因此在新版本中,基於plop,提供了幾個基礎模板,方便建立新的view或者component。 執行以下命令:

npm run new
複製代碼

plop

如上面 gif 所示,如今只要輕鬆的點幾回回車就能夠輕鬆生成我要的基礎代碼片斷。這裏只是一個 demo,你徹底能夠按照本身需求定製模板。老版本的vue-cli實現邏輯和它相似。

若是你以爲配置太複雜,我推薦你能夠安裝如 Vue 2 Snippets VS Code插件。 這種代碼片斷在平時工做中仍是能提高很多開發效率的。

async/await or promise

本次更新中,我也將部分代碼用了async/await的方式替代了原有的 promise方式,主要是 @/src/permission.js。有興趣的你們本身能夠經過 git-history 本身對比下,能夠發現代碼閱讀性高了很多。 不過本項目中也並無把全部promiseasync/await替代。我來簡單說一下個人見解。

6 個 Async/Await 優於 Promise 的方面,這篇文章不少人應該都看過,裏面大部分觀點我都是贊成的,大部分複雜場景下async/await的確是更優解。但相對的也不是全部的狀況下都是async/await寫起來讓我更爽的。先說說我最不爽的地方是它的錯誤處理,try catch讓這個代碼結構看起來就很奇怪(固然也有不少人很喜歡這種錯誤處理形式。社區也是相對的解決方案相似go語言的風格,好比 await-to-js

[err, res] = await to(getInfo())
if(err) //do something
複製代碼

這個方案是不錯,但還須要引入一個新的庫,增長了學習成本,得不償失。因此以我我的的習慣,當只有一個異步請求,且須要作錯誤處理的狀況下,更傾向於使用 promise。好比

// promise
getInfo()
  .then(res => {
    //do somethings
  })
  .catch(err => {
    //do somethings
  })

// async/await
try {
  const res = await getInfo()
  //do somethings
} catch (error) {
  //do somethings
}
複製代碼

在有嵌套請求的狀況下,確定是 async/await 更直觀的。

// promise
a(() => {
  b(() => {
    c()
  })
})

// async/await
await a()
await b()
await c()
複製代碼

固然代碼寫的好與很差仍是取決於寫代碼的人的。好比一個常見的業務場景:有兩個併發的異步請求,在都完成後do something。但不少人會錯誤的用串行的方式實現了。

//錯誤
await a()
await b()
//這樣變成了 a().then(() => b() )
// a 好了纔會執行 b
done()

//正確
await Promise.all([a(), b()])
done()
複製代碼

還有一個小細節async/await打包後的代碼其實會比 promise 複雜不少, 固然這個是一個忽略不計得問題。

總結:我認爲它們兩我的並非or的關係,在特定的業務場景下,選擇相對而言代碼可讀性更好地解決方案。

以上所述純我的偏心,並不是什麼最佳實現。具體該怎麼選擇仍是須要你們更具本身團隊的風格或者本身的理解來判斷。

命名規範

其實剛開始我寫 vue 文件的時候也不注意,各類駝峯啊、大寫開頭 (PascalCase)仍是橫線鏈接 (kebab-case)混着來,誰叫 vue 均可以,在 風格指南 中也沒有定論。不過基於本項目我仍是整理了一套文件的命名規則。

Component

全部的Component文件都是以大寫開頭 (PascalCase),這也是官方所 推薦的

但除了 index.vue

例子:

  • @/src/components/BackToTop/index.vue
  • @/src/components/Charts/Line.vue
  • @/src/views/example/components/Button.vue

JS 文件

全部的.js文件都遵循橫線鏈接 (kebab-case)。

例子:

  • @/src/utils/open-window.js
  • @/src/views/svg-icons/require-icons.js
  • @/src/components/MarkdownEditor/default-options.js

Views

views文件下,表明路由的.vue文件都使用橫線鏈接 (kebab-case),表明路由的文件夾也是使用一樣的規則。

例子:

  • @/src/views/svg-icons/index.vue
  • @/src/views/svg-icons/require-icons.js

使用橫線鏈接 (kebab-case)來命名views主要是出於如下幾個考慮。

  • 橫線鏈接 (kebab-case) 也是官方推薦的命名規範之一 文檔
  • views下的.vue文件表明的是一個路由,因此它須要和component進行區分(component 都是大寫開頭)
  • 頁面的url 也都是橫線鏈接的,好比https://www.xxx.admin/export-excel,因此路由對應的view應該要保持統一
  • 沒有大小寫敏感問題

CDN

你能夠經過執行npm run preview -- --report來分析webpack打包以後的結果,觀察各個靜態資源的大小。你能夠發現佔用空間最多的是第三方依賴。如vueelement-uiECharts等。

你可使用 CDN 外鏈的方式引入這些第三方庫,這樣能大大增長構建的速度(經過 CDN 引入的資源不會經 webpack 打包)。若是你的項目沒有本身的CDN服務的話,使用一些第三方的CDN服務,如 jsdelivrunpkg 等是一個很好的選擇,它提供過了免費的資源加速,同時提供了緩存優化,因爲你的第三方資源是在html中經過script引入的,它的緩存更新策略都是你本身手動來控制的,省去了你須要優化緩存策略功夫。

不少文章說使用 CDN 引入的方式能大大減少代碼的體積,這是不可能的。雖然打包完的 bundle小了,但那部分代碼只是被你拆出去,用CDN的方式引入罷了。你想減少體積,最高效的方案是啓用GZIP

我我的暫時不使用CDN引入第三方依賴的緣由:

暫時構建速度尚未遇到什麼瓶頸,全部沒有必要單獨剝離部分第三方依賴。使用CDN引入的方式等於一些第三方依賴的版本你是經過package.json來控制的,一些依賴則須要手動維護,增長了一些維護成本。目前基於 webpack 的optimization.splitChunks已經作了資源的緩存優化,靜態資源的緩存已經作得很好了。而且目前全部的靜態資源都會上傳到本身的CDN服務,沒有必要使用第三方的CDN服務。

固然全部的優化都是須要結合本身的具體業務來調整的! 以後可能會採用這種引入方式,或者使用webpack dll的方式進行優化。若是你以爲CDN引入對於的項目有益處,你能夠遵循以下方法進行修改:

使用方式

先找到 vue.config.js, 添加 externalswebpack 不打包 vueelement

externals: {
  vue: 'Vue',
  'element-ui':'ELEMENT'
}
複製代碼

而後配置那些第三方資源的CDN,請注意前後順序。

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。

<head>
  <!-- 引入樣式 -->
  <% for(var css of htmlWebpackPlugin.options.cdn.css) { %>
    <link rel="stylesheet" href="<%=css%>">
  <% } %>
</head>

<!-- 引入JS -->
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
  <script src="<%=js%>"></script>
<% } %>
複製代碼

完整的 代碼修改

最終你可使用 npm run preview -- --report 查看效果 如圖:

同理,其它第三方依賴均可以使用相同的方式處理(好比vuexvue-router等)。固然你也能夠選擇使用 DLLPlugin的方式來處理第三方依賴,從而來優化構建。

小技巧與建議

Watch immediate

這個已經算是一個比較常見的技巧了,這裏就簡單說一下。當 watch 一個變量的時候,初始化時並不會執行,以下面的例子,你須要在created的時候手動調用一次。

// bad
created() {
  this.fetchUserList();
},
watch: {
  searchText: 'fetchUserList',
}
複製代碼

你能夠添加immediate屬性,這樣初始化的時候也會觸發,而後上面的代碼就能簡化爲:

// good
watch: {
  searchText: {
    handler: 'fetchUserList',
    immediate: true,
  }
}
複製代碼

ps: watch 還有一個容易被你們忽略的屬性deep。當設置爲true時,它會進行深度監聽。簡而言之就是你有一個 const obj={a:1,b:2},裏面任意一個 key 的 value 發生變化的時候都會觸發watch。應用場景:好比我有一個列表,它有一堆query篩選項,這時候你就能deep watch它,只有任何一個篩序項改變的時候,就自動請求新的數據。或者你能夠deep watch一個 form 表單,當任何一個字段內容發生變化的時候,你就幫它作自動保存等等。

Attrs 和 Listeners

這兩個屬性是 vue 2.4 版本以後提供的,它簡直是二次封裝組件或者說寫高階組件的神器。在咱們平時寫業務的時候免不了須要對一些第三方組件進行二次封裝。好比咱們須要基於el-select分裝一個帶有業務特性的組件,根據輸入的 name 搜索用戶,並將一些業務邏輯分裝在其中。但el-select這個第三方組件支持幾十個配置參數,咱們固然能夠適當的挑選幾個參數經過 props 來傳遞,但萬一哪天別人用你的業務組件的時候以爲你的參數少了,那你只能改你封裝的組件了,亦或是哪天第三方組件加入了新參數,你該怎麼辦?

其實咱們的這個組件只是基於el-select作了一些業務的封裝,好比添加了默認的placeholder,封裝了遠程 ajax 搜索請求等等,總的來講它就是一箇中間人組件,只負責傳遞數據而已。

這時候咱們就可使用v-bind="$attrs":傳遞全部屬性、v-on="$listeners"傳遞全部方法。以下圖所示:

這樣,咱們沒有在$props中聲明的方法和屬性,會經過$attrs$listeners直接傳遞下去。這兩個屬性在咱們平時分裝第三方組件的時候很是有用!

.sync

這個也是 vue 2.3 以後新加的一個語法糖。這也是平時在分裝組件的時候很好用的一個語法糖,它的實現機制和v-model是同樣的。

當你有須要在子組件修改父組件值的時候這個方法很好用。 線上例子

Computed 的 get 和 set

computed 你們確定都用過,它除了能夠緩存計算屬性外,它在處理傳入數據和目標數據格式不一致的時候也是頗有用的。set、get 文檔

上面說的可能仍是是有點抽象,舉一個簡單的的例子:咱們有一個 form 表單,from 裏面有一個記錄建立時間的字段create_at。咱們知道前端的時間戳都是 13 位的,但不少後端默認時間戳是 10 位的,這就很蛋疼了。前端和後端的時間戳位數不一致。最多見的作法以下:

上面的代碼主要作的是:在拿到數據的時候將後端 10 位時間戳轉化爲 13 位時間戳,以後再向服務端發送數據的時候再轉化回 10 位時間戳傳給後端。目前這種作法固然是可行的,但以後可能不只只有建立接口,還有更新接口的時候,你還須要在update的接口裏在作一遍一樣數據轉化的操做麼?並且這只是一個最簡單的例子,真實的 form 表單會複雜的多,須要處理的數據也更爲的多。這時候代碼就會變得很難維護。

這時候就可使用 computed 的 set 和 get 方法了。

經過上面的代碼能夠看到,咱們把須要作先後端兼容的數據,放在了 computed 中,從 getDatasubmit中隔離了數據處理的部分。

固然上面說的方案還不是最好的方案,你其實應該利用以前所說的v-bind="$attrs"v-on="$listeners"對時間選擇器組件進行二次封裝。例如這樣<date-time v-model="postForm.create_at" /> 外部無需作任何數據處理,直接傳入一個 10 位的時間戳,內部進行轉化。當日期發生變化的時候,自動經過emit觸發input使v-model發生變化,把全部髒活累活都放在組件內部完成,保持外部業務代碼的相對乾淨。具體 v-model 語法糖原理能夠見官方 文檔

set 和 get 處理能夠作上面說的進行一些數據處理以外,你也能夠把它當作一個 watch的升級版。它能夠監聽數據的變化,當發生變化時,作一些額外的操做。最經典的用法就是v-model上綁定一個 vuex 值的時候,input 發生變化時,經過 commit更新存在 vuex 裏面的值。

具體的解釋你也能夠見官方 文檔

Object.freeze

這算是一個性能優化的小技巧吧。在咱們遇到一些 big data的業務場景,它就頗有用了。尤爲是作管理後臺的時候,常常會有一些超大數據量的 table,或者一個含有 n 多數據的圖表,這種數據量很大的東西使用起來最明顯的感覺就是卡。但其實不少時候其實這些數據其實並不須要響應式變化,這時候你就可使用 Object.freeze 方法了,它能夠凍結一個對象(注意它不併是 vue 特有的 api)。

當你把一個普通的 JavaScript 對象傳給 Vue 實例的 data 選項,Vue 將遍歷此對象全部的屬性,並使用 Object.defineProperty 把這些屬性所有轉爲 getter/setter,它們讓 Vue 能進行追蹤依賴,在屬性被訪問和修改時通知變化。 使用了 Object.freeze 以後,不只能夠減小 observer 的開銷,還能減小很多內存開銷。相關 issue

使用方式:this.item = Object.freeze(Object.assign({}, this.item))

這裏我提供了一個在線測速 demo,點我

經過測速能夠發現正常狀況下1000 x 10 rerender 都穩定在 1000ms-2000ms 之間,而開啓了Object.freeze的狀況下,rerender 都穩住在 100ms-200ms 之間。有接近 10 倍的差距。因此能肯定不須要變化檢測的狀況下,big data 仍是要優化一下的。

Functional

函數式組件 這個是文檔裏就寫的內容,但在其實不多人會刻意的去使用。由於你不用它,代碼也不會有任何問題,用了到可能會出現 bug。

咱們先看一個例子:點我測試性能 肉眼可見的性能差距。固然不少人會以爲個人項目中也沒有這種變化量級,但我以爲這是一個程序員的自我修養問題吧。,好比能用v-show的地方就不要用v-if,善用keep-alivev-onceObject.freeze()處理 vue big data 問題等。雖然都是一些小細節,但對性能和體驗都是有很多的提高的。更多的性能優化技巧請查看該文章 vue-9-perf-secrets

減小全局操做

這其實並不僅是針對 vue 項目的一個建議,咱們平時寫代碼的時候必定要儘可能避免一些全局的操做。若是必需要用到的時候,必定要本身檢查,會不會產生一些全局的污染或者反作用。

舉幾個簡單例子:

  1. 咱們如今雖然用 vue 寫代碼了,核心思想轉變爲用數據驅動 view,不用像jQuery時代那樣,頻繁的操做 DOM 節點。但仍是免不了有些場景仍是要操做 DOM 的。咱們在組件內選擇節點的時候必定要切記避免使用 document.querySelector()等一系列的全局選擇器。你應該使用this.$el或者this.refs.xxx.$el的方式來選擇 DOM。這樣就能將你的操做侷限在當前的組件內,能避免不少問題。

  2. 咱們常常會不可避免的須要註冊一些全局性的事件,好比監聽頁面窗口的變化window.addEventListener('resize', this.__resizeHandler),但再聲明瞭以後必定要在 beforeDestroy或者destroyed生命週期註銷它。window.removeEventListener('resize', this.__resizeHandler)避免形成沒必要要的消耗。

  3. 避免過多的全局狀態,不是全部的狀態都須要存在 vuex 中的,應該根據業務進行合理的進行取捨。若是不可避免有不少的值須要存在 vuex 中,建議使用動態註冊的方式。相關文檔。只是部分業務須要的狀態處理,建議使用 Event Bus或者使用 簡單的 store 模式

  4. css 也應該儘可能避免寫太多的全局性的樣式。除了一些全局公用的樣式外,因此針對業務的或者組件的樣式都應該使用命名空間的方式或者直接使用 vue-loader 提供的 scoped寫法,避免一些全局衝突。文檔

Sass 和 Js 之間變量共享

這個需求可能有些人沒有遇到過,舉個實際例子來講明一下。

如上面要實現一個動態的換膚,就須要將用戶選擇的 theme 主題色傳遞給 css。但同時初始化的時候 css 又須要將一個默認主題色傳遞給 js。因此下面咱們就分兩塊來說解。

  • js 將變量傳遞給 sass 這部分是相對簡單就能夠實現的,實現方案也不少。最簡單的方法就是經過 在模板裏面寫 style 標籤來實現,就是俗話所說的內聯標籤。

    <div :style="{'background-color':color}" ></div>
    複製代碼

    或者使用 css var(),在線 demo,還有用 less 的話modifyVars,等等方案都能實現 js 與 css 的變量傳遞。

  • sass 將變量給 js

仍是那前面那個換膚來舉例子,咱們頁面初始化的時候,總須要一個默認主題色吧,假設咱們在 var.scss中聲明瞭一個 theme:blue,咱們在 js 中該怎麼獲取這個變量呢?咱們能夠經過 css-modules :export來實現。更具體的解釋-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
複製代碼

當 js 和 css 共享一個變量的時候這個方案仍是很實用的。vue-element-admin 中的側邊欄的寬度,顏色等等變量都是經過這種方案來實現共享的。

其它換膚方案能夠參考 聊一聊前端換膚

自動註冊全局組件

個人業務場景大部分是中後臺,雖然封裝和使用了不少第三方組件,但仍是免不了須要本身封裝和使用不少業務組件。但每次用的時候還須要手動引入,真的是有些麻煩的。

咱們其實能夠基於 webpack 的require.context來實現自動加載組件並註冊的全局的功能。相關原理在以前的文章中已經闡述過了。具體代碼以下

咱們能夠建立一個GlobalComponents文件夾,將你想要註冊到全局的組件都放在這個文件夾裏,在index.js裏面放上如上代碼。以後只要在入口文件main.js中引入便可。

//main.js
import './components/Table/index' // 自動註冊全局業務組件
複製代碼

這樣咱們能夠在模板中直接使用這些全局組建了。不須要再繁瑣的手動引入了。

<template>
  <div>
    <user-select/>
    <status-button/>
  </div>
</template>
複製代碼

固然你也不要爲了省事,啥組件都往全局註冊,這樣會讓你初始化頁面的時候你的初始init bundle很大。你應該就註冊那些你常用且體積不大的組件。那些體積大的組件,如編輯器或者圖表組件仍是按需加載比較合理。並且你最好聲明這些全局組件的時候有一個統一的命名規範好比:globel-user-select這樣的,指定一個團隊規範,否則人家看到你這個全局組件會一臉懵逼,這個組件是哪來的。

Lint

這又是一個老生常談的問題了 vue 的一些最佳實踐什麼的話,這裏不討論了,我以爲看官方的 風格指南 差很少就夠了。好比避免避免 v-if 和 v-for 用在一塊兒元素特性的順序這些等等規則,幾十條規則,說真的寫了這麼久 vue,我也只能記住一些常規的。什麼屬性的順序啊,不太可能記住的。這種東西仍是交給程序來自動優化纔是更合理的選擇。強烈推薦配置編輯器自動化處理。具體配置見 文檔。同時建議結合 Git Hooks 配合在每次提交代碼時對代碼進行 lint 校驗,確保全部提交到遠程倉庫的代碼都符合團隊的規範。它主要使用到的工具是huskylint-staged,詳細文檔見 Git Hooks

Hook

這個是一個文檔裏沒有寫的 api,但我以爲是一個頗有用的 api。好比咱們平時使用一些第三方組件,或者註冊一些全局事件的時候,都須要在mounted中聲明,在destroyed中銷燬。但因爲這個是寫在兩個生命週期內的,很容易忘記,並且大部分在建立階段聲明的內容都會有反作用,若是你在組件摧毀階段忘記移除的話,會形成內存的泄漏,並且都不太容易發現。以下代碼:

react 在新版本中也加入了useEffect,將之前的多個 life-cycles 合併、重組,使邏輯更加清晰,這裏就不展開了。那 vue 是否是也能夠這樣作?我去了看了一下官方的 vue-hooks源碼 發現了一個新的 api:$on('hook:xxx')。有了它,咱們就能將以前的代碼用更簡單和清楚地方式實現了。

和 react 的useEffect有殊途同歸之妙。

並且咱們有了這個 api 以後,能幹的事情還不止這個。有時候咱們會用一些第三方組件,好比咱們有一個編輯器組件(加載比較慢,會有白屏),因此咱們在它渲染完成以前須要給它一個佔位符,但可能這個組件並無暴露給咱們這個接口,固然咱們須要修改這個組件,在它建立的時候手動 emit 一個事件出去,而後在組件上監聽它,好比:

固然這也是可行的,但萬一還要監聽一個更新或者摧毀的生命週期呢?其實利用 hook能夠很方便的實現這個效果。

固然在 vue 3.0 版本中可能會有新的寫法,就不以下面的討論: Dynamic Lifecycle Injection。有興趣的能夠自行去研究,這裏就不展開了。當 3.0 正式發佈以後再來討論吧。

RoadMap

最後來講一下,以後須要作的事情吧:

  • 更好的多級頁面緩存:目前頁面的緩存基於keep-alive,但當三級路由嵌套的狀況下,支持的並很差。以後探索一個更好的解決方案。
  • 單元測試:當項目大了以後,沒有單元測試維護起來仍是有些吃力的。 以後會慢慢補上unit-test 的測試用例。 酌情加上一些e2e-test的例子。
  • 去國際化:其實大部分人是不須要國際化的,默認狀況下移除國際化。單獨開一個國際化分支(v4.1 已完成)。
  • 適配 webpack5:webpack5 仍是解決了很多以前的痛點的,正式版發佈以後會進行升級。
  • vue 3.0: 等官方發佈以後會基於新版本進行重構(這個或許還有好久)
  • 適配 element-ui 3.0 以前官方發了 3.0 的打算(我也不知道會不會跳票)

總結

開源不易,且行且珍惜吧。

系列文章:

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息