欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue自定義指令directive的使用方法

 更新時間:2019年04月07日 11:43:37   作者:hfhan  
這篇文章主要介紹了vue自定義指令directive的使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

Vue中內(nèi)置了很多的指令,如v-model、v-show、v-html等,但是有時候這些指令并不能滿足我們,或者說我們想為元素附加一些特別的功能,這時候,我們就需要用到vue中一個很強大的功能了—自定義指令。

在開始之前,我們需要明確一點,自定義指令解決的問題或者說使用場景是對普通 DOM 元素進行底層操作,所以我們不能盲目的胡亂的使用自定義指令。

如何聲明自定義指令?

就像vue中有全局組件和局部組件一樣,他也分全局自定義指令和局部指令。

let Opt = {
 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){ },
}

對于全局自定義指令的創(chuàng)建,我們需要使用 Vue.directive接口

Vue.directive('demo', Opt)

對于局部組件,我們需要在組件的鉤子函數(shù)directives中進行聲明

Directives: {
 Demo:  Opt
}

Vue中的指令可以簡寫,上面Opt是一個對象,包含了5個鉤子函數(shù),我們可以根據(jù)需要只寫其中幾個函數(shù)。如果你想在 bind 和 update 時觸發(fā)相同行為,而不關(guān)心其它的鉤子,那么你可以將Opt改為一個函數(shù)。

let Opt = function(el,binding,vnode){ }

如何使用自定義指令?

對于自定義指令的使用是非常簡單的,如果你對vue有一定了解的話。

我們可以像v-text=”'test'”一樣,把我們需要傳遞的值放在‘='號后面?zhèn)鬟f過去。

我們可以像v-on:click=”handClick” 一樣,為指令傳遞參數(shù)'click'。

我們可以像v-on:click.stop=”handClick” 一樣,為指令添加一個修飾符。

我們也可以像v-once一樣,什么都不傳遞。

每個指令,他的底層封裝肯定都不一樣,所以我們應該先了解他的功能和用法,再去使用它。

自定義指令的 鉤子函數(shù)

上面我們也介紹了,自定義指令一共有5個鉤子函數(shù),他們分別是:bind、inserted、update、componentUpdate和unbind。

一個指令定義對象可以提供如下幾個鉤子函數(shù) (均為可選):

  • bind:只調(diào)用一次,指令第一次綁定到元素時調(diào)用。在這里可以進行一次性的初始化設置。
  • inserted:被綁定元素插入父節(jié)點時調(diào)用 (僅保證父節(jié)點存在,但不一定已被插入文檔中)。
  • update:所在組件的 VNode 更新時調(diào)用,但是可能發(fā)生在其子 VNode 更新之前。指令的值可能發(fā)生了改變,也可能沒有。但是你可以通過比較更新前后的值來忽略不必要的模板更新 (詳細的鉤子函數(shù)參數(shù)見下)。
  • componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新后調(diào)用。
  • unbind:只調(diào)用一次,指令與元素解綁時調(diào)用。

指令鉤子函數(shù)會被傳入以下參數(shù):

  • el:指令所綁定的元素,可以用來直接操作 DOM 。
  • binding:一個對象,包含以下屬性:
    • name:指令名,不包括 v- 前綴。
    • value:指令的綁定值,例如:v-my-directive="1 + 1" 中,綁定值為 2。
    • oldValue:指令綁定的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。
    • expression:字符串形式的指令表達式。例如 v-my-directive="1 + 1" 中,表達式為 "1 + 1"。
    • arg:傳給指令的參數(shù),可選。例如 v-my-directive:foo 中,參數(shù)為 "foo"。
    • modifiers:一個包含修飾符的對象。例如:v-my-directive.foo.bar 中,修飾符對象為 { foo: true, bar: true }。
  • vnode:Vue 編譯生成的虛擬節(jié)點。移步 VNode API 來了解更多詳情。
  • oldVnode:上一個虛擬節(jié)點,僅在 update 和 componentUpdated 鉤子中可用。

對于這幾個鉤子函數(shù),了解的可以自行跳過,不了解的我也不介紹,自己去官網(wǎng)看,沒有比官網(wǎng)上說的更詳細的了:鉤子函數(shù)

項目中的bug

在項目中,我們自定義一個全局指令my-click

Vue.directive('my-click',{
 bind:function(el, binding, vnode, oldVnode){
  el.addEventListener('click',function(){
   console.log(el, binding.value)
  })
 }
})

同時,有一個數(shù)組arr:[1,2,3,4,5,6],我們遍歷數(shù)組,生成dom元素,并為元素綁定指令:

<ul>
 <li v-for="(item,index) in arr" :key="index" v-my-click="item">{{item}}</li>
</ul>

可以看到,當我們點擊元素的時候,成功打印了元素,以及傳遞過去的數(shù)據(jù)。

可是,當我們把最后一個元素動態(tài)的改為8之后(6 --> 8),點擊元素,元素是對的,可是打印的數(shù)據(jù)卻仍然是6.

或者,當我們刪除了第一個元素之后,點擊元素

黑人問號臉,這是為什么呢????帶著這個疑問,我去看了看源碼。在進行下面的源碼分析之前,先來說結(jié)論:

組件進行初始化的時候,也就是第一次運行指令的時候,會執(zhí)行bind鉤子函數(shù),我們所傳入的參數(shù)(binding)都進入到了這里,并形成了一個閉包。

當我們進行數(shù)據(jù)更新的時候,vue虛擬dom不會銷毀這個組件(如果說刪除某個數(shù)據(jù),會從后往前銷毀組件,前面的總是最后銷毀),而是進行更新(根據(jù)數(shù)據(jù)改變),如果指令有update鉤子會運行這個鉤子函數(shù),但是對于元素在bind中綁定的事件,在update中沒有處理的話,他不會消失(依然引用初始化時形成的閉包中的數(shù)據(jù)),所以當我們更改數(shù)據(jù)再次點擊元素后,看到的數(shù)據(jù)還是原數(shù)據(jù)。

源碼分析

函數(shù)執(zhí)行順序:createElm/initComponent/patchVnode --> invokeCreateHooks (cbs.create) --> updateDirectives --> _update

在createElm方法和initComponent方法和更新節(jié)點patchVnode時會調(diào)用invokeCreateHooks方法,它會去遍歷cbs.create中鉤子函數(shù)進行執(zhí)行,cbs.create中的鉤子函數(shù)如下圖所示共8個。我們所需要看的就是updateDirectives這個函數(shù),這個函數(shù)會繼續(xù)調(diào)用_update函數(shù),vue中的指令操作就都在這個_update函數(shù)中了。

下面我們就來詳細看下這個_update函數(shù)。

function _update(oldVnode, vnode) {
 //判斷舊節(jié)點是不是空節(jié)點,是的話表示新建/初始化組件
 var isCreate = oldVnode === emptyNode;
 //判斷新節(jié)點是不是空節(jié)點,是的話表示銷毀組件
 var isDestroy = vnode === emptyNode;
 //獲取舊節(jié)點上的所有自定義指令
 var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);
 //獲取新節(jié)點上的所有自定義指令
 var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context);

 //保存inserted鉤子函數(shù)
 var dirsWithInsert = [];
 //保存componentUpdated鉤子函數(shù)
 var dirsWithPostpatch = [];

 var key, oldDir, dir;
 
 //這里先說下callHook$1函數(shù)的作用
 //callHook$1有五個參數(shù),第一個參數(shù)是指令對象,第二個參數(shù)是鉤子函數(shù)名稱,第三個參數(shù)新節(jié)點,
 //第四個參數(shù)是舊節(jié)點,第五個參數(shù)是是否為注銷組件,默認為undefined,只在組件注銷時使用
 //在這個函數(shù)里,會根據(jù)我們傳遞的鉤子函數(shù)名稱,運行我們自定義組件時,所聲明的鉤子函數(shù),
 
 //遍歷所有新節(jié)點上的自定義指令
 for(key in newDirs) {
  oldDir = oldDirs[key];
  dir = newDirs[key];
  //如果舊節(jié)點中沒有對應的指令,一般都是初始化的時候運行
  if(!oldDir) {
   //對該節(jié)點執(zhí)行指令的bind鉤子函數(shù)
   callHook$1(dir, 'bind', vnode, oldVnode);
   //dir.def是我們所定義的指令的五個鉤子函數(shù)的集合
   //如果我們的指令中存在inserted鉤子函數(shù)
   if(dir.def && dir.def.inserted) {
    //把該指令存入dirsWithInsert中
    dirsWithInsert.push(dir);
   }
  } else { 
   //如果舊節(jié)點中有對應的指令,一般都是組件更新的時候運行
   //那么這里進行更新操作,運行update鉤子(如果有的話)
   //將舊值保存下來,供其他地方使用(僅在 update 和 componentUpdated 鉤子中可用)
   dir.oldValue = oldDir.value;
   //對該節(jié)點執(zhí)行指令的update鉤子函數(shù)
   callHook$1(dir, 'update', vnode, oldVnode);
   //dir.def是我們所定義的指令的五個鉤子函數(shù)的集合
   //如果我們的指令中存在componentUpdated鉤子函數(shù)
   if(dir.def && dir.def.componentUpdated) {
    //把該指令存入dirsWithPostpatch中
    dirsWithPostpatch.push(dir);
   }
  }
 }
 
 //我們先來簡單講下mergeVNodeHook的作用
 //mergeVNodeHook有三個參數(shù),第一個參數(shù)是vnode節(jié)點,第二個參數(shù)是key值,第三個參數(shù)是回函數(shù)
 //mergeVNodeHook會先用一個函數(shù)wrappedHook重新封裝回調(diào),在這個函數(shù)里運行回調(diào)函數(shù)
 //如果該節(jié)點沒有這個key屬性,會新增一個key屬性,值為一個數(shù)組,數(shù)組中包含上面說的函數(shù)wrappedHook
 //如果該節(jié)點有這個key屬性,會把函數(shù)wrappedHook追加到數(shù)組中
 
 //如果dirsWithInsert的長度不為0,也就是在初始化的時候,且至少有一個指令中有inserted鉤子函數(shù)
 if(dirsWithInsert.length) {
  //封裝回調(diào)函數(shù)
  var callInsert = function() {
   //遍歷所有指令的inserted鉤子
   for(var i = 0; i < dirsWithInsert.length; i++) {
    //對節(jié)點執(zhí)行指令的inserted鉤子函數(shù)
    callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);
   }
  };
  if(isCreate) {
   //如果是新建/初始化組件,使用mergeVNodeHook綁定insert屬性,等待后面調(diào)用。
   mergeVNodeHook(vnode, 'insert', callInsert);
  } else {
   //如果是更新組件,直接調(diào)用函數(shù),遍歷inserted鉤子
   callInsert();
  }
 }
 
 //如果dirsWithPostpatch的長度不為0,也就是在組件更新的時候,且至少有一個指令中有componentUpdated鉤子函數(shù)
 if(dirsWithPostpatch.length) {
  //使用mergeVNodeHook綁定postpatch屬性,等待后面子組建全部更新完成調(diào)用。
  mergeVNodeHook(vnode, 'postpatch', function() {
   for(var i = 0; i < dirsWithPostpatch.length; i++) {
    //對節(jié)點執(zhí)行指令的componentUpdated鉤子函數(shù)
    callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
   }
  });
 }
 
 //如果不是新建/初始化組件,也就是說是更新組件
 if(!isCreate) {
  //遍歷舊節(jié)點中的指令
  for(key in oldDirs) {
   //如果新節(jié)點中沒有這個指令(舊節(jié)點中有,新節(jié)點沒有)
   if(!newDirs[key]) {
    //從舊節(jié)點中解綁,isDestroy表示組件是不是注銷了
    //對舊節(jié)點執(zhí)行指令的unbind鉤子函數(shù)
    callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);
   }
  }
 }
}

callHook$1函數(shù)

function callHook$1(dir, hook, vnode, oldVnode, isDestroy) {
 var fn = dir.def && dir.def[hook];
 if(fn) {
  try {
   fn(vnode.elm, dir, vnode, oldVnode, isDestroy);
  } catch(e) {
   handleError(e, vnode.context, ("directive " + (dir.name) + " " + hook + " hook"));
  }
 }
}

解決

看過了源碼,我們再回到上面的bug,我們應該如何去解決呢?

1、事件解綁,重新綁定

我們在bind鉤子中綁定了事件,當數(shù)據(jù)更新后,會運行update鉤子,所以我們可以在update中先解綁再重新進行綁定。因為bind和update中的內(nèi)容差不多,所以我們可以把bind和update合并為同一個函數(shù),在用自定義指令的簡寫方法寫成下面的代碼:

Vue.directive('my-click', function(el, binding, vnode, oldVnode){
 //點擊事件的回調(diào)掛在在元素myClick屬性上
 el.myClick && el.removeEventListener('click', el.myClick);
 el.addEventListener('click', el.myClick = function(){
  console.log(el, binding.value)
 })
})

可以看到,數(shù)據(jù)已經(jīng)變成我們想要的數(shù)據(jù)了。

2、把binding掛在到元素上,更新數(shù)據(jù)后更新binding

我們已經(jīng)知道了,造成問題的根本原因是初始化運行bind鉤子的時候為元素綁定事件,事件內(nèi)獲取的數(shù)據(jù)是初始化的時候傳遞過來的數(shù)據(jù),因為形成了閉包,那么我們不使用能引起閉包的數(shù)據(jù),把數(shù)據(jù)存到某一個地方,然后去更新這個數(shù)據(jù)。

Vue.directive('my-click',{
 bind: function(el, binding, vnode, oldVnode){
  el.binding = binding
  el.addEventListener('click', function(){
   var binding = this.binding
   console.log(this, binding.value)
  })
 },
 update: function(el, binding, vnode, oldVnode){
  el.binding = binding
 }
})

這樣也能達到我們想要的效果。

3、更新父元素

如果我們?yōu)楦冈豼l綁定一個變化的key值,這樣,當數(shù)據(jù)變更的時候就會更新父元素,從而重新創(chuàng)建子元素,達到重新綁定指令的效果。

<ul :key="Date.now()">
 <li v-for="(item,index) in arr" :key="index" v-my-click="item">{{item}}</li>
</ul>

這樣也能達到我們想要的效果。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論