基础介绍
除了核心功能默认内置的指令 (v-model
和 v-show
),Vue 也允许注册自定义指令。自定义指令可以对普通 DOM 元素进行底层操作。
基本语法
局部指令:
new Vue({
directives:{
{
指令名:配置对象},
{
指令名:配置对象}
}
//或
directives:{
{
指令名:回调函数},
{
指令名:回调函数}
}
//两种写法可以混合使用,如
directives:{
{
指令名:回调函数},
{
指令名:配置对象}
}
})
全局指令:
Vue.directive(指令名:配置对象)
//或
Vue.directive(指令名:回调函数)
//两种写法可以混合使用
局部指令的函数写法
需求:定义一个v-big指令,和v-text功能类似,但可以把绑定的数值放大10倍。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue自定义指令练习</title>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="root">
<h2>当前的n值是:<span v-text="n"></span> </h2>
<h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el:'#root',
data:{
n:1
},
//局部指令------directives:{指令名:回调函数} 写法
directives:{
big(element,binding){
console.log(binding)
console.log(element)
console.dir(element)
},
}
})
</script>
</body>
</html>
指令的执行时机
直接在浏览器中打开开发者调试工具,可以看到console.log的内容已经被打印
其中,指令里的this指向的是全局window对象
这说明:当指令写在标签内时,指令函数就被调用执行! 此外,更改v-bid后面的值,指令也会执行。
1.指令与元素成功绑定时(一上来)。
2.指令所在的模板被重新解析时。
指令接受的参数
big指令接受了两个参数(均为形参)
element binding
binding(绑定)
{
"name": "big",
"rawName": "v-big",
"value": 1,
"expression": "n",
"modifiers": {
},
"def": {
}
}
binding:一个对象,包含以下 property:
name:指令名,不包括 v- 前缀。
- rawname:指令名,包括 v- 前缀。
- value:指令的绑定值,例如:v-big="n" 中,绑定值为 1。
- expression:字符串形式的指令表达式。例如 v-big="n"中,表达式为 "n"。
- def: {bind: ƒ, update: ƒ}:。
- modifiers:。
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
element
- element :指令所绑定的元素,可以用来直接操作 DOM。
<span></span>
console.log(element)打印出了span标签(实际上span这个Dom对象)
accessKey: ""
ariaAtomic: null
ariaAutoComplete: null
ariaBusy: null
ariaChecked: null
ariaColCount: null
ariaColIndex: null
ariaColSpan: null
ariaCurrent: null
ariaDescription: null
ariaDisabled: null
ariaExpanded: null
ariaHasPopup: null
ariaHidden: null
ariaKeyShortcuts: null
ariaLabel: null
ariaLevel: null
ariaLive: null
ariaModal: null
ariaMultiLine: null
ariaMultiSelectable: null
ariaOrientation: null
ariaPlaceholder: null
ariaPosInSet: null
ariaPressed: null
ariaReadOnly: null
ariaRelevant: null
ariaRequired: null
ariaRoleDescription: null
ariaRowCount: null
ariaRowIndex: null
ariaRowSpan: null
ariaSelected: null
ariaSetSize: null
ariaSort: null
ariaValueMax: null
ariaValueMin: null
ariaValueNow: null
ariaValueText: null
assignedSlot: null
attributeStyleMap: StylePropertyMap {
size: 0}
attributes: NamedNodeMap {
length: 0}
autocapitalize: ""
autofocus: false
baseURI: "file:///D:/%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%96%99/Vue%E7%9B%B8%E5%85%B3%E4%BB%A3%E7%A0%81/vue2_%E5%9F%BA%E7%A1%80/16_%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%87%E4%BB%A4/index.html"
childElementCount: 0
childNodes: NodeList []
children: HTMLCollection []
classList: DOMTokenList [value: '']
className: ""
clientHeight: 0
clientLeft: 0
clientTop: 0
clientWidth: 0
contentEditable: "inherit"
dataset: DOMStringMap {
}
dir: ""
draggable: false
elementTiming: ""
enterKeyHint: ""
firstChild: null
firstElementChild: null
hidden: false
id: ""
innerHTML: ""
innerText: ""
inputMode: ""
isConnected: true
isContentEditable: false
lang: ""
lastChild: null
lastElementChild: null
localName: "span"
namespaceURI: "http://www.w3.org/1999/xhtml"
nextElementSibling: null
nextSibling: null
nodeName: "SPAN"
nodeType: 1
nodeValue: null
nonce: ""
offsetHeight: 32
offsetLeft: 245
offsetParent: body
offsetTop: 72
offsetWidth: 0
onabort: null
onanimationend: null
onanimationiteration: null
onanimationstart: null
onauxclick: null
onbeforecopy: null
onbeforecut: null
onbeforepaste: null
onbeforexrselect: null
onblur: null
oncancel: null
oncanplay: null
oncanplaythrough: null
onchange: null
onclick: null
onclose: null
oncontextmenu: null
oncopy: null
oncuechange: null
oncut: null
ondblclick: null
ondrag: null
ondragend: null
ondragenter: null
ondragleave: null
ondragover: null
ondragstart: null
ondrop: null
ondurationchange: null
onemptied: null
onended: null
onerror: null
onfocus: null
onformdata: null
onfullscreenchange: null
onfullscreenerror: null
ongotpointercapture: null
oninput: null
oninvalid: null
onkeydown: null
onkeypress: null
onkeyup: null
onload: null
onloadeddata: null
onloadedmetadata: null
onloadstart: null
onlostpointercapture: null
onmousedown: null
onmouseenter: null
onmouseleave: null
onmousemove: null
onmouseout: null
onmouseover: null
onmouseup: null
onmousewheel: null
onpaste: null
onpause: null
onplay: null
onplaying: null
onpointercancel: null
onpointerdown: null
onpointerenter: null
onpointerleave: null
onpointermove: null
onpointerout: null
onpointerover: null
onpointerrawupdate: null
onpointerup: null
onprogress: null
onratechange: null
onreset: null
onresize: null
onscroll: null
onsearch: null
onsecuritypolicyviolation: null
onseeked: null
onseeking: null
onselect: null
onselectionchange: null
onselectstart: null
onslotchange: null
onstalled: null
onsubmit: null
onsuspend: null
ontimeupdate: null
ontoggle: null
ontransitioncancel: null
ontransitionend: null
ontransitionrun: null
ontransitionstart: null
onvolumechange: null
onwaiting: null
onwebkitanimationend: null
onwebkitanimationiteration: null
onwebkitanimationstart: null
onwebkitfullscreenchange: null
onwebkitfullscreenerror: null
onwebkittransitionend: null
onwheel: null
outerHTML: "<span></span>"
outerText: ""
ownerDocument: document
parentElement: h2
parentNode: h2
part: DOMTokenList [value: '']
prefix: null
previousElementSibling: null
previousSibling: text
scrollHeight: 0
scrollLeft: 0
scrollTop: 0
scrollWidth: 0
shadowRoot: null
slot: ""
spellcheck: true
style: CSSStyleDeclaration {
accentColor: '', additiveSymbols: '', alignContent: '', alignItems: '', alignSelf: '', …}
tabIndex: -1
tagName: "SPAN"
textContent: ""
title: ""
translate: true
virtualKeyboardPolicy: ""
完善实例
上述例子中,v-big的功能还没有实现,因为在自定义插件的big函数中我们还没有进行操作。
我们需要将拿到的值(binding.value)* 10 赋值给dom元素的innerText
element.innerText = binding.value *10
<!DOCTYPE html>
<html lang="en">
<head>
.....
</head>
<body>
...
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el:'#root',
data:{
n:1},
directives:{
big(element,binding){
element.innerText = binding.value *10
},
}
})
</script>
</body>
</html>
局部指令的对象写法
定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
<body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="root">
<input type="text" v-fbind:value="n">
<button @click = 'n++'>点击加1</button>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el:'#root',
data:{
n:100
},
directives:{
fbind(element,binding){
element.value = binding.value //将绑定的值n(data中的100)赋值给input的value值
element.focus() //input自动获取焦点
},
}
})
</script>
</body>
</html>;
打开页面后并没有立即获取到焦点
但是点击按钮后获取到了焦点
这是为什么呢?先来回顾一下原生Dom的操作。
原生Dom创建一个自动获取焦点的输入框
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
<style>
.demo{
background-color: orange;}
</style>
</head>
<body>
<button id="btn">点我创建一个输入框</button>
<script type="text/javascript" >
const btn = document.getElementById('btn')
btn.onclick = ()=>{
const input = document.createElement('input')
document.body.appendChild(input) //将创建的input元素插入到页面中
input.focus()
}
</script>
</body>
</html>
上述代码创建了一个输入框,并将其插入页面后自动获取焦点。这是可行的。
但是,如果将获取焦点的代码写在input插入页面之前,功能是无法实现的,因为这个时候页面还没有input元素,自然无法获取焦点。
btn.onclick = ()=>{
const input = document.createElement('input')
input.focus()
document.body.appendChild(input) //将创建的input元素插入到页面中
}
补充:其他Dom操作的书写时机参考
input.className = 'demo'
input.value = 99
input.onclick = ()=>{alert(1)}
document.body.appendChild(input) //将创建的input元素插入到页面中
input.parentElement.style.backgroundColor = 'skyblue'
可见,input获取焦点的时机是至关重要的。
vue中element.focus() 不生效的原因解析
<div id="root">
<input type="text" v-fbind:value="n">
<button @click = 'n++'>点击加1</button>
</div>
v-fbind:value="n" 是元素的绑定过程,指v-fbind与input的value指进行绑定,绑定完成后,执行了directives内的函数。但此时
当点击button按钮式,此时input输入框已经真正的渲染在页面上了,因此,此时,input输入框会自动获取焦点。
可见,由于“directives:{指令名:回调函数} ”的定义方法中回调函数执行时机的问题,导致element.focus() 无法实现自动获取焦点的功能。
解决这一问题,可以使用 directives的对象写法。
directives的对象写法
语法回顾:directives:{指令名:配置对象}
配置对象中拥有三个内置函数bind、inserted、update。
bind:{
//1.指令与元素成功绑定时(一上来)
bind(element,binding){
console.log('首先执行')
},
//2.指令所在元素被插入页面时
inserted(element,binding){
console.log('其次执行')
},
//3.指令所在的模板被重新解析时
update(element,binding){
console.log('点击执行')
}
}
因此,我们可以将代码进行优化,完成input输入框自动获取焦点功能。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>自定义指令</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>{
{name}}</h2>
<h2>当前的n值是:<span v-text="n"></span> </h2>
<!-- <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2> -->
<h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
<button @click="n++">点我n+1</button>
<hr/>
<input type="text" v-fbind:value="n">
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el:'#root',
data:{
name:'222',
n:1
},
directives:{
big(element,binding){
element.innerText = binding.value * 10
},
fbind:{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
}
}
})
</script>
</html>
指令总结
定义语法:
(1).局部指令:
new Vue({
directives:{
指令名:配置对象} 或 directives{
指令名:回调函数}
})
(2).全局指令:
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
配置对象中常用的3个回调:
(1).bind:指令与元素成功绑定时调用。
(2).inserted:指令所在元素被插入页面时调用。
(3).update:指令所在模板结构被重新解析时调用。
备注
- 指令定义时不加v-,但使用时要加v-;
- 指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
全局组件的使用思路
直接使用
Vue.directive(big, {
inserted(element,binding){
element.text = "哈哈哈哈哈哈"
}
})
Vue.directive(big2, function(element,binding){
element.text = "你是狗"
})
Vue.directive(big3, (element,binding) => {
element.text = "你是狗!!!"
})
使用插件形式
// 自定义 MyPlugin 插件
//MyPlugin必须暴露一个install方法给Vue.use,没有install方法会把里面的函数作为install方法
Vue.use(MyPlugin)
new Vue({
// ...组件选项
})
MyPlugin = function(Vue, options){
Vue.directive('my-directive', {
...})
Vue.directive('my-directive', {
...})
}
Vue.use(MyPlugin)
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...}
// 2. 添加全局资源()自定义指令
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
全局注册多个自定义插件
直接书写多个Vue.directive可以注册多个自定义插件,但是这样比较麻烦
Vue.directive(big, {
inserted(element,binding){
element.text = "哈哈哈哈哈哈"
}
})
Vue.directive(big2, function(element,binding){
element.text = "你是狗"
})
Vue.directive(big3, (element,binding) => {
element.text = "你是狗!!!"
})
因此,我们可以使用插件安装的方式进行优化。
比如,我们现在定义了一个allDirectives对象,这个对象里写了很多个自定义指令,并把它写在函数里给暴露出去。
const allDirectives = {
big:{
inserted(element,binding){
}
},
big1:function(element,binding){
},
big2(element,binding){
},
big3:(element,binding)=> {
}
}
const allDirectivesFunction = function ( Vue,options ){
Vue.directive('big',allDirectives.big)
Vue.directive('big1',allDirectives.big1)
Vue.directive('big2',allDirectives.big2)
Vue.directive('big3',allDirectives.big3)
}
export default allDirectivesFunction
然后再main.js中引入使用
// 需要注册的全局指令
import directive from './directives'
Vue.use(directive)
接下来,将allDirectivesFunction进行简化
const allDirectives = {
big:{
inserted(element,binding){
}
},
big1:function(element,binding){
},
big2(element,binding){
},
big3:(element,binding)=> {
}
}
const allDirectivesFunction = function ( Vue,options ){
Object.keys(allDirectives).forEach((name) => Vue.directive(name, allDirectives[name]))
}
export default allDirectivesFunction
const allDirectives = {
big:{
inserted(element,binding){ }
},
big1:function(element,binding){ },
big2(element,binding){ },
big3:(element,binding)=> { }
}
export default ( Vue,options ) => {
Object.keys(allDirectives).forEach((name) => Vue.directive(name, allDirectives[name]))
}