本文转载于SegmentFault社区
作者:Ray_
在使用 Element 组件库时,有些需求功能是组件库不带有的,使用了自定义指令来解决这些问题
代码仓库vue-element-utils
也提供了 npm 包下载使用
入口文件引入:
例如:
Select 滚动加载(懒加载)
Dialog 拖拽位置
Dialog 拖拽宽高
Vue自定义指令概念
钩子函数Vue 提供了内置指令v-model,v-show等,也允许注册自定义指令。多数情况中代码的复用和抽象的主要形式是组件。有的情况你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
bind: 只调用一次,指令第一次绑定到元素时调用。这里可以进行一次性的初始化设置
inserted: 被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)
update: 所在组件的 VNode 更新时调用
componentUpdated: 所在组件的 VNode 及其子 VNode 全部更新后调用
unbind: 只调用一次,指令与元素解绑时调用
除了el之外,其它参数都应该是只读的。如果需要在钩子之间共享数据,建议通过元素的dataset来进行。
el: 指令所绑定的元素,可以用来直接操作 DOM
binding: 一个对象,包含以下 property:
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 编译生成的虚拟节点
oldVnode: 上一个虚拟节点, 仅在update和componentUpdated钩子中可用
Select 滚动加载(懒加载)
当 Select 组件需要展示的数据非常多的时候,滚动加载可以作为一种优化手段,当然还有其它更好的方式,可以参考Vue项目优化总结。
展示:
selectWrap.addEventListener( 'scroll', function( ) { /*** scrollHeight 获取元素内容高度(只读)** scrollTop 获取或者设置元素的偏移量,常用于:计算滚动条的位置,当一个元素的容器没有产生方向的滚动条,那它的 scrollTop 的值默认为0** clientHeight 读取元素的可见高度(只读)** 如果元素滚动到底, 等式`ele.scrollHeight - ele.scrollTop === ele.clientHeight;`返回 true, 没有则返回 false*/constcondition = this.scrollHeight - this.scrollTop <= this.clientHeight; condition && binding.value;});}}
Dialog 拖动
展示:
// 获取原有属性 ie dom 元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);conststy = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
dialogHeaderEl.onmousedown = ( e) => { // 鼠标按下,计算当前元素距离可视区的距离constdisX = e.clientX - dialogHeaderEl.offsetLeft; constdisY = e.clientY - dialogHeaderEl.offsetTop;
// 获取到的值带 px 正则匹配替换letstyL, styT;
// 注意在 ie 中 第一次获取到的值为组件自带 50% 移动之后赋值为 pxif(sty.left.includes( '%')) { styL = + document.body.clientWidth * (+sty.left.replace( /%/g, '') / 100); styT = + document.body.clientHeight * (+sty.top.replace( /%/g, '') / 100); } else{ styL = +sty.left.replace( /px/g, ''); styT = +sty.top.replace( /px/g, ''); }
document.onmousemove = function( e) { // 通过事件委托,计算移动的距离constl = e.clientX - disX; constt = e.clientY - disY;
// 移动当前元素dragDom.style.left = ` ${l + styL}px` ; dragDom.style.top = ` ${t + styT}px` ;
//将此时的位置传出去//binding.value({x:e.pageX,y:e.pageY})};
document.onmouseup = function( e) { document.onmousemove = null; document.onmouseup = null; };};}}
Dialog 拖拽宽度
展示:
// 当前宽度constcurWidth = dragDom.offsetWidth;
document.onmousemove = function( e) { e.preventDefault; // 移动时禁用默认事件
// 通过事件委托,计算移动的距离constl = e.clientX - disX;
dragDom.style.width = ` ${curWidth + l}px` ; };
document.onmouseup = function( e) { document.onmousemove = null; document.onmouseup = null; };},false);dragDom.(lineEl);}}
clipboard 剪切板
展示:
functionclipboard( text) { returnnewPromise( ( reslove, reject) => { constinput = document.( 'input'); input.setAttribute( 'readonly', 'readonly'); input.setAttribute( 'value', text); document.body.(input); input.focus;input.setSelectionRange( 0, 9999); if( document.execCommand( 'copy')) { document.execCommand( 'copy'); reslove(text);} else{ reject( newError( '复制失败')); }document.body.removeChild(input); });}const$clipboard = clipboard;
export{ $clipboard}
exportdefault{ bind: function( el, binding, vnode) { if(binding.arg === 'success') { el._clipboard_success = binding.value;} elseif(binding.arg === 'error') { el._clipboard_error = binding.value;} else{ el._clipboard_message = binding.value;eventUtil.addHandler(el, 'click', => { // log(el._clipboard_message);clipboard(el._clipboard_message).then( msg=> { el._clipboard_success(msg);}).catch( err=> { el._clipboard_error(err);});});}},unbind: function( el, binding) { if(binding.arg === 'success') { deleteel._clipboard_success; } elseif(binding.arg === 'error') { deleteel._clipboard_error; } else{ deleteel._clipboard_message; eventUtil.removeHandler(el, 'click'); }}}
结语
将一些操作 DOM 的方法改为指令方式使用,在 Vue 中可以大大减轻工作量
将一些拓展性高的自定义指令打包到npm上,多个项目都可以使用,减少Ctrl C代码,且可维护性高,也方便有相同需求的同学可以直接使用