vue自定義指令要點
- 2020 年 3 月 9 日
- 筆記
vue自定義指令的基礎使用這裡就不闡述,看官網文檔:https://cn.vuejs.org/v2/guide/custom-directive.html
本文用一個實例描述自定義指令的要點,自定義一個數據上報的指令。
你可能會這樣寫demo:
// 自定義v-datacenter命令埋點,點擊節點發送埋點數據 // demo : <div v-datacenter="{ei: 'learning_center_click'}">進入學習中心</div> const dataCenter = function(data){ // 這裡處理數據上報 } Vue.directive('datacenter', { bind(el, binding) { el._dataCenter = function(el) { dataCenter(binding.value); } el.addEventListener('click', el._dataCenter) }, unbind(el) { // 移除監聽 el.removeEventListener('click', el._dataCenter); delete el._dataCenter; } })
這個demo對節點的點擊做了響應,處理了數據埋點。有什麼問題呢?只處理了初次綁定的數據,如果你的數據是通過ajax非同步獲取的,就可能出現問題,比如
<div v-datacenter="{ei: info.dataEvent}">進入學習中心</div>
其中info.dataEvent最開始是空字元,從後台拉取數據以後info.dataEvent才有值。那麼上面的自定義指令中bind中的binding.value的值應當是為空字元。點擊上報數據時“ei”的值一直為空字元
改進:
// 自定義v-datacenter命令埋點,點擊節點發送埋點數據 // demo : <div v-datacenter="{ei: 'learning_center_click'}">進入學習中心</div> Vue.directive('datacenter', { bind(el, binding) { el._dataCenter = function(el) { dataCenter(binding.value); } el.addEventListener('click', el._dataCenter) }, update(el, binding) { // 處理value一開始沒有值,後面才有值的情況 if (binding.value && (JSON.stringify(binding.value) !== JSON.stringify(binding.oldValue))) { // 移除之前的監聽 el.removeEventListener('click', el._dataCenter); delete el._dataCenter; // 新增監聽 el._dataCenter = function(el) { dataCenter(binding.value); } el.addEventListener('click', el._dataCenter) } }, unbind(el) { // 移除監聽 el.removeEventListener('click', el._dataCenter); delete el._dataCenter; } })
添加了update(當所在組件的 VNode 更新時調用),由於update時指令的value可能完全沒有改動,所以要判斷當值有更改且有效時重新綁定click監聽。這樣和bind配合就滿足了同步/非同步的所有場景。
真的就OK了么?顯然不是,還有一種異常情況:在特殊情況下(如路由切換),節點既要響應click監聽,也要移除節點。unbind就會在響應click監聽之前調用。監聽在響應之前就被移除,導致失敗。
二次改進:
// 自定義v-datacenter命令埋點,點擊節點發送埋點數據 // demo : <div v-datacenter="{ei: 'learning_center_click'}">進入學習中心</div> Vue.directive('datacenter', { bind(el, binding) { el._dataCenter = function(el) { dataCenter(binding.value); } el.addEventListener('click', el._dataCenter) }, update(el, binding) { // 處理value一開始沒有值,後面才有值的情況 if (binding.value && (JSON.stringify(binding.value) !== JSON.stringify(binding.oldValue))) { // 移除之前的監聽 el.removeEventListener('click', el._dataCenter); delete el._dataCenter; // 新增監聽 el._dataCenter = function(el) { dataCenter(binding.value); } el.addEventListener('click', el._dataCenter) } }, unbind(el) { // 移除監聽 // 在特殊情況下節點既要響應click,也要移除節點。避免在響應click之前就被移除監聽, // 所以要延時移除,放到下一個宏任務 setTimeout(() => { el.removeEventListener('click', el._dataCenter); delete el._dataCenter; }) } })
要避免在響應監聽前監聽被移除,所以將移除監聽放到下一個宏任務。OK,收工!
如果覺得本文不錯,請點擊右下方【推薦】!