开始之前我们先学习⼀下指令系统这个词指令系统是计算机硬件的语⾔系统,也叫机器语⾔,它是系统程序员看到的计算机的主要属性。因此指令系统表征了计算机的基本功能决定了机器所要求的能⼒。
在 vue 中提供了⼀套为数据驱动视图更为⽅便的操作,这些操作被称为指令系统,我们看到的v-
开头的⾏内属性,都是指令,不同的指令可以完成或实现不同的功能除了核⼼功能默认内置的指令(v-model
和v-show
),Vue
也允许注册⾃定义指令指令使⽤的⼏种⽅式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| v-xxx
v-xxx="value"
v-xxx="'string'"
v-xxx:arg="value"
v-xxx:arg.modifier="value"
|
如何实现
注册⼀个⾃定义指令有全局注册与局部注册,全局注册主要是通过Vue.directive
⽅法进⾏注册,Vue.directive
第⼀个参数是指令的名字(不需要写上v-
前缀),第⼆个参数可以是对象数据,也可以是⼀个指令函数.
1 2 3 4 5 6 7 8
| Vue.directive('focus', { inserted: function (el) { el.focus() }, })
|
局部注册通过在组件options
选项中设置directive
属性
1 2 3 4 5 6 7 8
| directives: { focus: { inserted: function (el) { el.focus() } } }
|
然后你可以在模板中任何元素上使⽤新的v-focus
property,如下:
⾃定义指令也像组件那样存在钩⼦函数:
- bind :只调⽤⼀次,指令第⼀次绑定到元素时调⽤。在这⾥可以进⾏⼀次性的初始化设置
- inserted :被绑定元素插⼊⽗节点时调⽤ (仅保证⽗节点存在,但不⼀定已被插⼊⽂档中)
- update :所在组件的
VNode
更新时调⽤,但是可能发⽣在其⼦VNode
更新之前。指令的值可能发⽣了改变,也可能没有。但是你可以通过⽐较更新前后的值来忽略不必要的模板更新
- componentUpdated :指令所在组件的
VNode
及其⼦VNode
全部更新后调⽤
- unbind :只调⽤⼀次,指令与元素解绑时调⽤
所有的钩⼦函数的参数都有以下:
- 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
钩⼦中可⽤
除了el
之外,其它参数都应该是只读的,切勿进⾏修改。如果需要在钩⼦之间共享数据,建议通过元素的dataset
来进⾏
举个例⼦:
1 2 3 4 5 6 7
| <div v-demo="{ color: 'white', text: 'hello!' }"></div> <script> Vue.directive('demo', function (el, binding) { console.log(binding.value.color) console.log(binding.value.text) }) </script>
|
应⽤场景
使⽤⾃定义指令可以满⾜我们⽇常⼀些场景,这⾥给出⼏个⾃定义指令的案例:
- 表单防⽌重复提交
- 图⽚懒加载
- ⼀键 Copy 的功能
表单防⽌重复提交
表单防⽌重复提交这种情况设置⼀个v-throttle
⾃定义指令来实现
举个例⼦:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| Vue.directive('throttle', { bind: (el, binding) => { let throttleTime = binding.value; if (!throttleTime) { throttleTime = 2000; } let cbFun; el.addEventListener('click', event => { if (!cbFun) { cbFun = setTimeout(() => { cbFun = null; }, throttleTime); } else { event && event.stopImmediatePropagation(); } }, true); }, });
<button @click="sayHello" v-throttle>提交</button>
|
图⽚懒加载
设置⼀个v-lazy
⾃定义指令完成图⽚懒加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| const LazyLoad = { install(Vue, options) { let defaultSrc = options.default; Vue.directive('lazy', { bind(el, binding) { LazyLoad.init(el, binding.value, defaultSrc); }, inserted(el) { if ('IntersectionObserver' in window) { LazyLoad.observe(el); } else { LazyLoad.listenerScroll(el); }
}, }) }, init(el, val, def) { el.setAttribute('data-src', val); el.setAttribute('src', def); }, observe(el) { let io = new IntersectionObserver(entries => { let realSrc = el.dataset.src; if (entries[0].isIntersecting) { if (realSrc) { el.src = realSrc; el.removeAttribute('data-src'); } } }); io.observe(el); }, listenerScroll(el) { let handler = LazyLoad.throttle(LazyLoad.load, 300); LazyLoad.load(el); window.addEventListener('scroll', () => { handler(el); }); }, load(el) { let windowHeight = document.documentElement.clientHeight let elTop = el.getBoundingClientRect().top; let elBtm = el.getBoundingClientRect().bottom; let realSrc = el.dataset.src; if (elTop - windowHeight < 0 && elBtm > 0) { if (realSrc) { el.src = realSrc; el.removeAttribute('data-src'); } } }, throttle(fn, delay) { let timer; let prevTime; return function(...args) { let currTime = Date.now(); let context = this; if (!prevTime) prevTime = currTime; clearTimeout(timer);
if (currTime - prevTime > delay) { prevTime = currTime; fn.apply(context, args); clearTimeout(timer); return; } timer = setTimeout(function() { prevTime = Date.now(); timer = null; fn.apply(context, args); }, delay); } } } export default LazyLoad;
|
⼀键Copy的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| import { Message } from 'ant-design-vue'; const vCopy = {
bind(el, { value }) { el.$value = value; 还会⽤ 到 el.handler = () => { if (!el.$value) { Message.warning('⽆复制内容'); return; } const textarea = document.createElement('textarea'); 出可视区域 textarea.readOnly = 'readonly'; textarea.style.position = 'absolute'; textarea.style.left = '-9999px'; textarea.value = el.$value; document.body.appendChild(textarea); textarea.select(); const result = document.execCommand('Copy'); if (result) { Message.success('复制成功'); } document.body.removeChild(textarea); }; el.addEventListener('click', el.handler); }, componentUpdated(el, { value }) { el.$value = value; }, unbind(el) { el.removeEventListener('click', el.handler); }, }; export default vCopy;
|
关于⾃定义指令还有很多应⽤场景,如:拖拽指令、⻚⾯⽔印、权限校验等等应⽤场景.