前言
Vue.js是一套構建使用者介面的漸進式框架(官方說明)。通俗點來說,Vue.js是一個輕量級的,易上手易使用的,便捷,靈活性強的前端MVVM框架。簡潔的API,良好健全的中文文件,使開發者能夠較容易的上手Vue框架。
本系列文章將結合個人在使用Vue中的一些經(cai)驗(keng)和一些案例,對Vue框架掌握的部分知識進行輸出,同時也鞏固對Vue框架的理解。
Vue自定義指令
簡述
Vue除了提供了預設內建的指令外,還允許開發人員根據實際情況自定義指令,它的作用價值在於當開發人員在某些場景下需要對普通DOM元素進行操作的時候。
註冊自定義指令
Vue自定義指令和元件一樣存在著全域性註冊和區域性註冊兩種方式。先來看看註冊全域性指令的方式,通過 Vue.directive( id, [definition] )
方式註冊全域性指令,第一個引數為自定義指令名稱(指令名稱不需要加 v-
字首,預設是自動加上字首的,使用指令的時候一定要加上字首),第二個引數可以是物件資料,也可以是一個指令函式。
<div id="app" class="demo">
<!-- 全域性註冊 -->
<input type="text" placeholder="我是全域性自定義指令" v-focus>
</div>
<script>
Vue.directive("focus", {
inserted: function(el){
el.focus();
}
})
new Vue({
el: "#app"
})
</script>
複製程式碼
這個簡單案例當中,我們通過註冊一個
v-focus
指令,實現了在頁面載入完成之後自動讓輸入框獲取到焦點的小功能。其中inserted
是自定義指令的鉤子函式,後面的內容會詳細講解。
全域性註冊好了,那麼再來看看如何註冊區域性自定義指令,通過在Vue例項中新增
directives
物件資料註冊區域性自定義指令。
<div id="app" class="demo">
<!-- 區域性註冊 -->
<input type="text" placeholder="我是區域性自定義指令" v-focus2>
</div>
<script>
new Vue({
el: "#app",
directives: {
focus2: {
inserted: function(el){
el.focus();
}
}
}
})
</script>
複製程式碼
鉤子函式
一個指令定義物件可以提供如下幾個鉤子函式 (均為可選):
-
bind:只呼叫一次,指令第一次繫結到元素時呼叫。在這裡可以進行一次性的初始化設定
-
inserted:被繫結元素插入父節點時呼叫 (僅保證父節點存在,但不一定已被插入文件中)。
-
update:所在元件的 VNode 更新時呼叫,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前後的值來忽略不必要的模板更新 。
-
componentUpdated:指令所在元件的 VNode 及其子 VNode 全部更新後呼叫。
-
unbind:只呼叫一次,指令與元素解綁時呼叫。
這段是從官方文件copy來的,相信應該都一看就明白的。
那麼這幾個鉤子函式怎麼使用呢?先來看看鉤子函式的幾個引數吧。指令鉤子函式會被傳入以下引數:
-
el: 指令所繫結的元素,可以用來直接操作 DOM,就是放置指令的那個元素。
-
binding: 一個物件,裡面包含了幾個屬性,這裡不多展開說明,官方文件上都有很詳細的描述。
-
vnode:Vue 編譯生成的虛擬節點。
-
oldVnode:上一個虛擬節點,僅在 update 和 componentUpdated 鉤子中可用。
自定義指令也可以傳遞多個值,可以用javascript表示式字面量傳遞,看例子:
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
<script>
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // "white"
console.log(binding.value.text) // "hello!"
})
</script>
複製程式碼
說了這麼多理論知識,那麼現在就來動手寫一個簡單的案例吧。假設這樣的看一個場景:當你在閱覽某網站的圖片時,可能會由於圖片資源比較大而載入緩慢,需要消耗一小段時間來呈現到眼前,這個體驗肯定是不太友好的(就像網站切換頁面,有時候會載入資源比較慢,為了給使用者較好的體驗,一般都會先出一個正在載入的友好提示頁面),所以這個案例的功能就是在圖片資源還沒載入出來時,先顯示預設背景圖,當圖片資源真正載入出來了之後,再把真實圖片放置到對應的位置上並顯示出來。
<div id="app2" class="demo">
<div v-for="item in imageList">
<img src="../assets/image/bg.png" alt="預設圖" v-image="item.url">
</div>
</div>
<script>
Vue.directive("image", {
inserted: function(el, binding) {
//為了真實體現效果,用了延時操作
setTimeout(function(){
el.setAttribute("src", binding.value);
}, Math.random() * 1200)
}
})
new Vue({
el: "#app2",
data: {
imageList: [
{
url: "http://consumer-img.huawei.com/content/dam/huawei-cbg-site/greate-china/cn/mkt/homepage/section4/home-s4-p10-plus.jpg"
},
{
url: "http://consumer-img.huawei.com/content/dam/huawei-cbg-site/greate-china/cn/mkt/homepage/section4/home-s4-watch2-pro-banner.jpg"
},
{
url: "http://consumer-img.huawei.com/content/dam/huawei-cbg-site/en/mkt/homepage/section4/home-s4-matebook-x.jpg"
}
]
}
})
</script>
複製程式碼
原始碼解讀
Vuetify 框架庫中,有提供幾種自定義指令API,包括瀏覽器視窗縮放 v-resize
,瀏覽器滾動條滑動 v-scroll
等自定義指令,現在就來學習一波 Vuetify 中自定義指令原始碼吧。
v-resize 自定義指令
在 src/directives/resize.js
中,是 v-resize
自定義指令操作的核心程式碼。
function inserted (el, binding) {
//指令的繫結值,是一個function函式
const callback = binding.value
//延時執行函式的毫秒數
const debounce = binding.arg || 200
//禁止執行與事件關聯的預設動作
const options = binding.options || { passive: true }
let debounceTimeout = null
const onResize = () => {
clearTimeout(debounceTimeout)
debounceTimeout = setTimeout(callback, debounce, options)
}
//監聽視窗縮放
window.addEventListener('resize', onResize, options)
//儲存監聽視窗縮放事件的引數,方便在unbind鉤子函式中解除事件繫結的時候使用到
el._onResize = {
callback,
options
}
if (!binding.modifiers || !binding.modifiers.quiet) {
onResize()
}
}
//繫結的DOM元素被移除時觸發
function unbind (el, binding) {
const { callback, options } = el._onResize
window.removeEventListener('resize', callback, options)
delete el._onResize
}
export default {
//指令名稱
name: 'resize',
inserted,
unbind
}
複製程式碼
可以看到,定義了 inserted
和 unbind
兩個鉤子函式,unbind 鉤子函式是用來解除監聽事件的。inserted 鉤子函式中,繫結監聽了視窗縮放事件並執行回撥函式,並採用簡單的函式防抖來防止操作過度頻繁,大致的流程就是這樣子的。
可能你會發現,上面的程式碼中,採用的都是es6標準語法寫的,對於還不太熟悉es6語法的童鞋來說,可能閱讀起來會比較的吃力,那麼下面就轉換成es5語法來完整的實現這個指令的功能,但是建議還是儘量去熟悉es6標準語法,因為這是前端發展程式中的必然趨勢。
function insertedFn (el, binding) {
var callback = binding.value;
var debounce = 200;
var options = {passive: true};
var debounceTimeout = null;
var onResize = function () {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(callback, debounce, options);
}
window.addEventListener("resize", onResize, options);
el._onResize = {
callback: callback,
options: options
};
}
function unbindFn (el, binding) {
var callback = el._onResize.callback;
var options = el._onResize.options;
window.removeEventListener("resize", callback, options);
delete el._onResize;
}
Vue.directive("resize", {
inserted: insertedFn,
unbind: unbindFn
})
複製程式碼
完整的案例可以點選這裡檢視:v-resize自定義指令。
Vuetify 中更多的自定義指令案例原始碼都可以在 github 中找到,附上 Vuetify 的 github 地址:github.com/vuetifyjs/v…。
總結
列舉的都是一些簡單的Vue自定義指令的知識,在實際專案中的不同場景會存在著各種坑。
Vue自定義指令還可以在圖片懶載入的場景下使用,vue-lazyload
就是有利用自定義指令實現圖片懶載入的,可能的話,後面可以分析一波 vue-lazyload
的原始碼。
後記
本著學習和總結的態度寫的文章,文中有任何錯誤和問題,可以在github上指出 issues 。文中的案例都放置在github上,地址:github.com/webproblem/…。