Vue的指令中實現傳遞更多參數
概要
我們在使用Vue的開發項目中,經常用自定義指令(directive)來封裝一系列的DOM操作,這樣做非常方便。一般來說,指令是使用動態指令參數來獲取App中的數據。
但是有些時候,自定義指令需要更多的數據來完成更復雜的功能,例如在指令中調用當前App實例的nextTick方法,以確保所有DOM元素加載完成,再進行DOM操作。還有一些情況,我們需要將一些全局配置參數傳遞給指令,已有的參數專遞方式,顯然無法滿足這些需求。
本文介紹一種擴展指令參數的方法,使其可以接收更多參數。該方法在Vue 2.0和 Vue 3.0中,都可以正常使用。
基本原理
本文介紹的指令擴展方法,主要以閉包為基礎,並且使用瞭一些函數參數柯裡化的方式來管理多個參數的傳遞過程。
我們以Vue2.0的指令定義方式為例,說明基本原理。本文所使用的指令定義方式,都已基於插件化的定義方式,在main.js中,通過use方法使用。
示例代碼如下:
const myDirective = { install(app,options){ app.directive("img-load", { bind:function(el,binding,vnode){ }, inserted:function(el,binding,vnode){ }, update:function(el,binding,vnode){ }, componentUpdated:function(el,binding,vnode){ }, unbind:function(el,binding,vnode){ }, }); } }; export default myDirective ;
按照上述標準的指令定義方式,無論使用哪個鉤子函數,我們隻能傳遞三個參數,指令所綁定的DOM元素,指令接收的APP中綁定參數和虛擬節點。
基於閉包的擴展方案
指令的鉤子函數參數已經固定,我們無法修改。但是我們可以通過閉包設置鉤子函數的作用域,讓閉包函數來接收更多參數。
代碼如下:
export default function getMyDirective(Vue) { return class MyDirective{ constructor(options) { this.options = options; this.bindDirective= this.bindDirective.bind(this); } bindDirective(el, bindings) { } } } const myDirective = { install(app,options){ const DirectiveClass = getMyDirective(app) ; var myDirective = new DirectiveClass(options); app.directive("my-dirctive", { bind:myDirective.bindDirective }); } };
- 使用閉包函getMyDirective來包裹鉤子函數bindDirective
- 閉包函數是用戶自定義函數,我們可以設置任意多個參數
- 在閉包函數中定義類來封裝指令的所有操作,構造方法也可以接收參數,從而將多個參數柯裡化分割。
- 通過bind方法強行將指令鉤子函數綁定的bindDirective方法的this限定為MyDirective的實例,也就是說,bindDirective方法可以通過this訪問更多的數據。
JS中函數具有獨立作用域,所以指令的綁定方法bindDirective在執行過程中,可以在不受任何外界其他代碼的幹擾下,使用閉包函數傳遞的參數。
實例和代碼實現
本文以一個圖片自動加載的指令為例,介紹自定義指令的參數擴展方式。
自定義指令的基本功能是根據圖片的URL地址加載並顯示圖片,具體實現包括:
- 通過指令動態參數獲取圖片地址
- 首先在頁面中顯示一個正在加載的圖片
- 加載指定地址圖片,如果加載成功,正常顯示
- 加載失敗,顯示一張加載出錯的圖片
本文以自頂向下的方式來介紹該實例的代碼實現
Main.js中將指令對應的插件全局化
使用use方法,在全局定義插件ImageLoad,該插件主要是功能是在全局定義一個圖片加載指令,為該指令接收一個全局配置,即加載中圖片地址和加載失敗的圖片地址。
Vue.use.use(ImageLoad, { loading: "http://localhost:4000/images/loading.gif", error: "http://localhost:4000/images/error.jpeg", });
ImageLoad插件定義
ImageLoad插件和其他插件一樣,既然要通過use使用,所以要定義install方法,install方法的第一個參數是當前App實例,第二個則是指令的全局配置。
import getImageLoad from './getImageLoad' const ImageLoad = { install(app,options){ const ImgClass = getImageLoad(app) ; var loadImage = new ImgClass(options); app.directive("img-load", { bind: loadImage.bindImage }); } }; export default ImageLoad;
- install方法中,首先通過調用getImageLoad方法,獲取加載圖片的管理類,傳入當前App實例。
- 實例化圖片加載管理類的對象loadImage ,傳入圖片加載的全局配置。
- 定義自定義指令v-img-load,該指定的bind鉤子方法指向loadImage中的bindImage方法。
- bindImage方法的this是指向loadImage對象,因此可以使用到App實例,指令全局配置,loadImage對象內的數據。
圖片加載管理類的定義
ImageLoadManagement定義瞭v-img-load指令的全部實現。
export default function getImageLoad(Vue) { return class ImageLoadManagement { constructor(options) { this.options = options; this.bindImage = this.bindImage.bind(this); this.renderImage = this.renderImage.bind(this); } bindImage(el, bindings) { const self = this; Vue.nextTick(function(){ const src = bindings.value; self.renderImage('loading', src, el); self.loadImage(src).then( () => self.renderImage('', src, el), () => self.renderImage('error', src, el), ); }); } loadImage(src) { return new Promise((resolve, reject) => { const img = new Image(); img.src = src; img.onload = resolve; img.onerror = reject; }); } renderImage(type, src, el) { let _src; const { error, loading } = this.options; switch (type) { case 'loading': _src = loading; break; case 'error': _src = error; break; default: _src = src; break; } el.setAttribute("src", _src); } } }
為瞭避免參數過多,所以采用柯裡化的方法,對參數進行瞭分割:
- 在閉包函數getImageLoad中,定義瞭全局App實例參數;
ImageLoadManagement
類的構造方法中定義瞭圖片加載指令需要的全局配置參數。class
作為function的語法糖使用,其本質來時function,從而實現獨立作用域。bindImage
方法中可以直接使用App實例的nextTick,無論該指令在父組件還是子組件中使用,都可以保證在指令中代碼執行時,所有DOM元素加載完成。loadImage
方法用於檢查指定URL的圖片是否存在,如果存在則顯示具體圖片,否則則顯示加載失敗的圖片。renderImage
方法用於設置指令綁定的Img元素的圖片地址,圖片的實際地址可以通過bindings參數的value屬性獲取。
通過上述方法,我們不僅擴展瞭指令的參數,使其可以支持更復雜的業務邏輯。
更重要的是。我們實現瞭指令的定義和實現邏輯的解耦,完全不再需要將所有的指令實現邏輯全部放在指令的註冊方法中。通過ImageLoadManagement 的定義,將所有的指令實現邏輯都內聚在其中。
Vue 3.0的實現
Vue 3.0中,指令參數的擴展方法思路與2.0一致,隻是因為Vue 3.0中指令的鉤子函數名稱與2.0不一致,造成一些區別。
具體代碼如下:
import getImageLoad from './getImageLoad' const ImageLoad = { install(app,options){ const ImgClass = getImageLoad(options) ; var loadImage = new ImgClass(); app.directive("img-load", { mounted: loadImage.bindImage }); } }; export default ImageLoad;
export default function getImageLoad(options) { return class ImageLoadManagement { constructor() { this.options = options; this.bindImage = this.bindImage.bind(this); this.renderImage = this.renderImage.bind(this); } bindImage(el, bindings) { const src = bindings.value; this.renderImage('loading', src, el); this.loadImage(src).then( () => this.renderImage('', src, el), () => this.renderImage('error', src, el), ); } loadImage(src) { return new Promise((resolve, reject) => { const img = new Image(); img.src = src; img.onload = resolve; img.onerror = reject; }); } renderImage(type, src, el) { let _src; const { error, loading } = this.options; switch (type) { case 'loading': _src = loading; break; case 'error': _src = error; break; default: _src = src; break; } el.setAttribute("src", _src); } } }
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Vue中使用裝飾器的方法詳解
- Vue3進階主題Composition API使用詳解
- 前端Vue中常用rules校驗規則詳解
- 一篇文章告訴你如何編寫Vue插件
- JS中ESModule和commonjs介紹及使用區別