在諸多 Vue.js 應用中, Lodash, Moment, Axios, Async等都是一些非常有用的 JavaScript 庫. 但隨著專案越來越複雜, 可能會採取元件化和模組化的方式來組織程式碼, 還可能要使應用支援不同環境下的服務端渲染. 除非你找到了一個簡單而又健壯的方式來引入這些庫供不同的元件和模組使用, 不然, 這些第三方庫的管理會給你帶來一些麻煩.
本文將介紹一些在 Vue.js 中使用第三方庫的方式.
全域性變數
在專案中新增第三方庫的最簡單方式是講其作為一個全域性變數, 掛載到 window 物件上:
entry.js
1 |
window._ = require('lodash'); |
MyComponent.vue
1 2 3 4 5 |
export default { created() { console.log(_.isEmpty() ? 'Lodash everywhere!' : 'Uh oh..'); } } |
這種方式不適合於服務端渲染, 因為服務端沒有 window 物件, 是 undefined, 當試圖去訪問屬性時會報錯.
在每個檔案中引入
另一個簡單的方式是在每一個需要該庫的檔案中匯入:
MyComponent.vue
1 2 3 4 5 6 7 |
import _ from 'lodash'; export default { created() { console.log(_.isEmpty() ? 'Lodash is available here!' : 'Uh oh..'); } } |
這種方式是允許的, 但是比較繁瑣, 並且帶來的問題是: 你必須記住在哪些檔案引用了該庫, 如果專案不再依賴這個庫時, 得去找到每一個引用該庫的檔案並刪除該庫的引用. 如果構建工具沒設定正確, 可能導致該庫的多份拷貝被引用.
優雅的方式
在 Vuejs 專案中使用 JavaScript 庫的一個優雅方式是講其代理到 Vue 的原型物件上去. 按照這種方式, 我們引入 Moment 庫:
entry.js
1 2 |
import moment from 'moment'; Object.defineProperty(Vue.prototype, '$moment', { value: moment }); |
由於所有的元件都會從 Vue 的原型物件上繼承它們的方法, 因此在所有元件/例項中都可以通過 this.$moment: 的方式訪問 Moment 而不需要定義全域性變數或者手動的引入.
MyNewComponent.vue
1 2 3 4 5 |
export default { created() { console.log('The time is ' . this.$moment().format("HH:mm")); } } |
接下來就瞭解下這種方式的工作原理.
Object.defineProperty
一般而言, 可以按照下面的方式來給物件設定屬性:
1 |
Vue.prototype.$moment = moment; |
可以這樣做, 但是 Object.defineProperty 允許我們通過一個 descriptor 來定義屬性. Descriptor 執行我們去設定物件屬性的一些底層(low-level)細節, 如是否允許屬性可寫? 是否允許屬性在 for 迴圈中被遍歷.
通常, 我們不會為此感到困擾, 因為大部分時候, 對於屬性賦值, 我們不需要考慮這樣的細節. 但這有一個明顯的優點: 通過 descriptor 建立的屬性預設是隻讀的.
這就意味著, 一些處於迷糊狀態的(coffee-deprived)開發者不能在元件內去做一些很愚蠢的事情, 就像這樣:
1 2 |
this.$http = 'Assign some random thing to the instance method'; this.$http.get('/'); // TypeError: this.$http.get is not a function |
此外, 試圖給只讀例項的方法重新賦值會得到 TypeError: Cannot assign to read only property 的錯誤.
$
你可能會注意到, 代理第三庫的屬性會有一個 $ 字首, 也可能看到其它類似 $refs, $on, $mount 的屬性和方式, 它們也有這個字首.
這個不是強制要求, 給屬性新增 $ 字首是提供那些處於迷糊狀態(coffee-deprived)的開發者這是一個公開的 API, 和 Vuejs 的一些內部屬性和方法區分開來.
this
你還可能注意到, 在元件內是通過 this.libraryName 的方式來使用第三方庫的, 當你知道它是一個例項方法時就不會感到意外了. 但與全域性變數不同, 通過 this 來使用第三方庫時, 必須確保 this 處於正確的作用域. 在回撥方法中 this 的作用域會有不同, 但箭頭式回撥風格能保證 this 的作用域是正確的:
1 2 3 4 5 6 |
this.$http.get('/').then(res => { if (res.status !== 200) { this.$http.get('/') // etc // Only works in a fat arrow callback. } }); |
外掛
如果你想在多個專案中使用同一個庫, 或者想將其分享給其他人, 可以將其寫成一個外掛:
1 2 |
import MyLibraryPlugin from 'my-library-plugin'; Vue.use(MyLibraryPlugin); |
在應用的入口引入外掛之後, 就可以在任何一個元件內像使用 Vue Router, Vuex 一樣使用你定義的庫了.
寫一個外掛
首先, 建立一個檔案用於編寫自己的外掛. 在示例中, 我會將 Axios 作為外掛新增到專案中, 因而我給檔案起名為 axios.js. 其次, 外掛要對外暴露一個 install 方法, 該方法的第一個引數是 Vue 的建構函式:
axios.js
1 2 3 4 5 |
export default { install: function(Vue) { // Do stuff } } |
可以使用先前將庫新增到原型物件上的方法:
axios.js
1 2 3 4 5 6 7 |
import axios from 'axios'; export default { install: function(Vue,) { Object.defineProperty(Vue.prototype, '$http', { value: axios }); } } |
最後, 利用 Vue 的例項方法 use 將外掛新增到專案中:
entry.js
1 2 3 4 5 6 7 8 |
import AxiosPlugin from './axios.js'; Vue.use(AxiosPlugin); new Vue({ created() { console.log(this.$http ? 'Axios works!' : 'Uh oh..'); } }) |
彩蛋: 外掛的可選引數
外掛的 install 方法可以接受可選引數. 一些開發可能不喜歡將 Axios 例項命名為 $http, 因為這是 Vue Resource 提供的一個通用名字. 因而可以提供一個可選的引數允許他們隨意命名:
axions.js
1 2 3 4 5 6 7 |
import axios from 'axios'; export default { install: function(Vue, name = '$http') { Object.defineProperty(Vue.prototype, name, { value: axios }); } } |
entry.js
1 2 3 4 5 6 7 8 |
import AxiosPlugin from './axios.js'; Vue.use(AxiosPlugin, '$axios'); new Vue({ created() { console.log(this.$axios ? 'Axios works!' : 'Uh oh..'); } }) |