vue自定义指令要点
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) {
// 或者使用dataset(要注意兼容性):https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/dataset 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,收工!