国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

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

2019-11-19 11:52:07
字體:
來源:轉載
供稿:網友

Vue中內置了很多的指令,如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){ },}

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

Vue.directive('demo', Opt)

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

Directives: { Demo:  Opt}

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

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

如何使用自定義指令?

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

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

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

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

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

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

自定義指令的 鉤子函數

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

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

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

指令鉤子函數會被傳入以下參數:

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

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

項目中的bug

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

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

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

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

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

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

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

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

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

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

源碼分析

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

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

下面我們就來詳細看下這個_update函數。

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

callHook$1函數

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鉤子中綁定了事件,當數據更新后,會運行update鉤子,所以我們可以在update中先解綁再重新進行綁定。因為bind和update中的內容差不多,所以我們可以把bind和update合并為同一個函數,在用自定義指令的簡寫方法寫成下面的代碼:

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

可以看到,數據已經變成我們想要的數據了。

2、把binding掛在到元素上,更新數據后更新binding

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

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、更新父元素

如果我們為父元素ul綁定一個變化的key值,這樣,當數據變更的時候就會更新父元素,從而重新創建子元素,達到重新綁定指令的效果。

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

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 蓝山县| 郓城县| 宁陵县| 洪雅县| 台安县| 安泽县| 焦作市| 静乐县| 鄯善县| 尼玛县| 潮安县| 中山市| 同江市| 西藏| 民勤县| 临湘市| 潞城市| 汉源县| 乐亭县| 政和县| 富民县| 文安县| 宁海县| 垫江县| 靖远县| 吴桥县| 视频| 观塘区| 鄂尔多斯市| 遂昌县| 马鞍山市| 西安市| 甘孜县| 潮州市| 浦江县| 曲沃县| 平舆县| 大港区| 桑日县| 吴桥县| 舒兰市|