前面一片文章說了vue2.0過濾器,其實自定義指令跟過濾器非常相似,單就定義方式而言,其與過濾器完全一致,分為局部指令,和全局指令。不過就是filter改為directive的區別。
過濾器一般用於對一些常見文本的格式化,而自定義指令主要是為了對底層DOM實現操作,雖然說vue主要是以數據驅動視圖,但是總有一些只能操作DOM的應用場景,例如最常見的:拖拽。此時,就是自定義指令大顯身手的時候了。
在移動端,常見這么一個需求,以微信為例,在微信主屏左右滑動時候,可以隨着滑動法相更改工具欄,也就是隨着滑動,可以在“微信”、“通訊錄”、“發現”、“我”之間隨意切換。
這類需求,必須是操作DOM實現的,而無法通過數據來驅動,所以這時候,不管是你使用插件,還是自己定義,必然都是通過自定義指令來實現左右滑動,而切換工具欄的。
為了方便操作,以tab切換為例:
<ul class="tabs"> <router-link to="/home" tag="li"><span>首頁</span></router-link> <router-link to="/product" tag="li"><span>產品</span></router-link> <router-link to="/about" tag="li"><span>關於我們</span></router-link> </ul>
在router-view上左右滑動,切換路由:
<router-view v-swipe-horizontal v-animate-css="'bounceInLeft'" appear></router-view>
我是利用Vue Animate Css來為路由切換添加了一個動畫的效果,這里暫不關注。這里需要關注的就是v-swipe-horizontal,“v-”是vue中約定的各種指令的寫法,所以其實真正的指令是swipe-horizontal,那么怎么定義swipe-horizontal呢?
全局定義:
// 注冊一個全局自定義指令 `v-focus` Vue.directive('swipe-horizontal', { bind: function (el, binding, vnode, oldVnode) { // 各種邏輯操做,這個指令中就是滑動的邏輯 } })
局部定義:
swipeHorizontal:{ bind(el, binding, vnode){ /** * 當前這種做法就完全依賴於路由配置的順序需要和tab選項卡保持一致, * 並且不能隨意添加其他位置的路由了 * 如果通過為每一個路由對象的index使用數組的find方法查找的話, * 則需要為路由對象添加index屬性 */ /* // 利用路由配置數組自身的順序進行判斷 let routes = vnode.context.$router.options.routes, xc = routes.find((item, index)=>{ return vnode.context.$route.path == item.path }), currentRouteIndex = routes.indexOf(xc), nextRoute = null, nextIndex = null; */ // 利用路有對象自定義的index屬性判斷 let routes = vnode.context.$router.options.routes, currentRoute = vnode.context.$route.path, currentRouteIndex = routes.find((item)=>{ return currentRoute == item.path }).index, nextRoute = null, nextIndex = null; var arr = routes.map((item)=>{ return item.index }) var max = Math.max(...arr) el.ontouchstart = (ev)=>{ let touch = ev.touches[0]; let disX = touch.clientX - el.offsetLeft; let x = disX; document.ontouchmove = (ev)=>{ let touch = ev.touches[0]; x = touch.clientX ; } document.ontouchend = () => { document.ontouchmove = null; document.ontouchend = null; console.log("移動距離: ", x, disX) /** * 需要利用(routes.length-1)的原因是 路由配置最后一個對象配置了路由重定向 */ // 利用當前的路由對象在整個路由中的序號,即路由配置的數組的順序 /* if (x - disX > 20) { nextIndex = (currentRouteIndex + 1) >= (routes.length-1) ? 0 : (currentRouteIndex + 1); } else if(disX - x > 20) { nextIndex = (currentRouteIndex - 1) < 0 ? routes.length -2 : (currentRouteIndex - 1); } */ nextRoute = routes[nextIndex] if (x - disX > 20) { nextIndex = (currentRouteIndex + 1) > max ? 0 : (currentRouteIndex + 1); } else if(disX - x > 20) { nextIndex = (currentRouteIndex - 1) < 0 ? max :(currentRouteIndex - 1); } if (nextIndex != null) { // 利用路有對象自定義的index屬性判斷 nextRoute = routes.find((item)=>{ return nextIndex == item.index }) //在自定義的指令中,只有通過vnode.context才能獲取到vue對象 vnode.context.$router.replace(nextRoute) } }
我這里提供了兩種確定路由的模式,主要是確定路由路徑的需要。
一種依賴路由數組的順序,但是這種情況下,路由數組的順序就必須與tab選項卡視圖展示的數據保持一致。
另外一種是為每一個路由添加一個index屬性,以確定當前tab選項卡展示的順序。
自定義指令中,主要邏輯是需要存在於自定義指令的鈎子函數中的,而鈎子函數又分為bind,inserted,update,componentUpdated,unbind五種,我不知道有多少人能夠用到所有的鈎子函數,從我的角度來說,常用的鈎子函數bind或者inserted,兩者選擇其一就可以了。
而鈎子函數中的參數:
真的已經很清楚了。
既然使用自定義指令了,那么盡量不要去修改vue的數據,但是如果非得修改,還是可以通過vnode.context獲取到當前的Vue對象的,到這一步,當前這個示例,剩下的也就是一些路由操作,以及基本的數組操作了。
其實,主要的就是分清楚什么情況下使用自定義指令,自定義指令的鈎子函數中el提供了當前操作的dom,binding提供了各類參數,vnode.context提供了當前的vue對象,然后就是邏輯操作了。各類需求不一致,邏輯就不可能相同,所以也沒什么好說的了。