Vue中的自定义指令怎么实现


今天小编给大家分享一下Vue中的自定义指令怎么实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。首先梳理思路:原生input控件与组件的实现方式需要区分,input的实现较为简单,我们先实现一下input的处理。
首先我们先定义一个不做任何操作的指令

Vue.directive('mymodel',{
//只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
bind(el,binding,vnode,oldVnode){
},
//被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中),需要父节点dom时使用这个钩子
inserted(el,binding,vnode,oldVnode){
},
//所在组件的VNode更新时调用,**但是可能发生在其子VNode更新之前**。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新(详细的钩子函数参数见下)。
update(el,binding,vnode,oldVnode){
},
//指令所在组件的VNode**及其子VNode**全部更新后调用。
componentUpdated(el,binding,vnode,oldVnode){
},
只调用一次,指令与元素解绑时调用。
unbind(el,binding,vnode,oldVnode){
},
})

上面的注释中详细的说明了各个钩子函数的调用时机,因为我们是给组件上添加input事件和value绑定,因此我们在bind这个钩子函数中定义即可。所以我们把其他的先去掉,代码变成这样。

Vue.directive('mymodel',{
//只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
bind(el,binding,vnode,oldVnode){
}
})

简单说一下bind函数的几个回调参数,el是指令绑定组件对应的dombinding是我们的指令本身,包含namevalueexpressionarg等,vnode就是当前绑定组件对应的vnode结点,oldVnode就是vnode更新前的状态。

上面的注释中详细的说明了各个钩子函数的调用时机,因为我们是给组件上添加input事件和value绑定,因此我们在bind这个钩子函数中定义即可。所以我们把其他的先去掉,代码变成这样。
简单说一下bind函数的几个回调参数,el是指令绑定组件对应的dombinding是我们的指令本身,包含namevalueexpressionarg等,vnode就是当前绑定组件对应的vnode结点,oldVnode就是vnode更新前的状态。
接下来我们要做两件事:绑定input事件,同步inputvalue值到外部value值绑定,监听value的变化,更新到inputvalue这对于input原生组件比较容易实现:

//第一步,添加inout事件监听
el.addEventListener('input',(e)=>{
//context是input所在的父组件,这一步是同步数据
vnode.context[binding.expression]=e.target.value;
})
//监听绑定的变量
vnode.context.$watch(binding.expression,(v)=>{
el.value=v;
})

这里解释一下上面的代码,vnode.context是什么呢,他就是我们指令所在组件的上下文环境,可以理解就是指令绑定的值所在的组件实例。不熟悉vnode结构的同学建议先看一下官方的文档,不过文档描述的比较简单,不是很全面,所以最好在控制台log一下vnode的对象看一下它具体的结构,这很有助于我们封装自定义指令,对理解Vue原理也很有帮助。

这里解释一下上面的代码,vnode.context是什么呢,他就是我们指令所在组件的上下文环境,可以理解就是指令绑定的值所在的组件实例。不熟悉vnode结构的同学建议先看一下官方的文档,不过文档描述的比较简单,不是很全面,所以最好在控制台log一下vnode的对象看一下它具体的结构,这很有助于我们封装自定义指令,对理解Vue原理也很有帮助。
我们可以通过context[binding.expression]获取v-model上到绑定的值,同样可以修改它。上面的代码中我们首先通过在添加的input事件中操作vnode.context[binding.expression] = e.target.value同步inputvalue值到外部(context),与使用@input添加事件监听效果是一样的;然后我们需要做第二件事,做value值的绑定,监听value的变化,同步值的变更到inputvalue上,我们想到我们可以使用Vue实例上的额$watch方法监听值的变化,而context就是那个Vue实例,binding.expression就是我们想要监听的属性,如果我们这样写

那么binding.expression就是字符串'message'。所以我们想下面的代码这样监听绑定的响应式数据

//监听绑定的变量
vnode.context.$watch(binding.expression,(v)=>{
el.value=v;
})

至此,inputv-mymodel的处理就完成了(当然input组件还有typecheckbox,radio,select等类型都需要去特别处理,这里就不再一一处理了,感兴趣的同学可以自己尝试去完善一下),但是对于非原生控件的组件,我们要特殊处理。
因此我们完善代码如下:

Vue.directive('mymodel',{
//只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
bind(el,binding,vnode,oldVnode){
//原生input组件的处理
if(vnode.tag==='input'){
//第一步,添加inout事件监听
el.addEventListener('input',(e)=>{
//context是input所在的父组件,这一步是同步数据
vnode.context[binding.expression]=e.target.value;
})
//监听绑定的变量
vnode.context.$watch(binding.expression,(v)=>{
el.value=v;
})
}else{//组件

}
}
})

接下来我们要处理的是自定义组件的逻辑,

//vnode的结构可以参见文档。不过我觉得最直观的方法就是直接在控制台打印处理
let{
componentInstance,
componentOptions,
context
}=vnode;
const{
_props
}=componentInstance;
//处理model选项
if(!componentOptions.Ctor.extendOptions.model){
componentOptions.Ctor.extendOptions.model={
value:'value',
event:'input'
}
}
letmodelValue=componentOptions.Ctor.extendOptions.model.value;
letmodelEvent=componentOptions.Ctor.extendOptions.model.event;
//属性绑定,这里直接修改了属性,没有想到更好的办法,友好的意见希望可以提出
_props[modelValue]=binding.value;
context.$watch(binding.expression,(v)=>{
_props[modelValue]=v;
})
//添加事件处理函数,做数据同步
componentInstance.$on(modelEvent,(v)=>{
context[binding.expression]=v;
})

声明一下,上面的实现不是vue源码的实现方式,vue源码中实现v-model更加复杂一点,是结合自定义指令、模板编译等去实现的,因为我们是应用级别的封装,所以采用了上述的方式实现。

那么binding.expression就是字符串'message'。所以我们想下面的代码这样监听绑定的响应式数据。
至此,inputv-mymodel的处理就完成了(当然input组件还有typecheckbox,radio,select等类型都需要去特别处理,这里就不再一一处理了,感兴趣的同学可以自己尝试去完善一下),但是对于非原生控件的组件,我们要特殊处理。
因此我们完善代码如下:

接下来我们要处理的是自定义组件的逻辑,
声明一下,上面的实现不是vue源码的实现方式,vue源码中实现v-model更加复杂一点,是结合自定义指令、模板编译等去实现的,因为我们是应用级别的封装,所以采用了上述的方式实现。
实现此v-mymodel需要同学去多了解一下VnodeComponentAPI,就像之前说的,最简单的方法就是直接在控制台中直接打印出vnode对象,组件的vnode上有Component的实例componentInstance。接下来简单说一下上面的代码,首先我们可以在componentOptions.Ctor.extendOptions上找到model的定义,如果没有的话需要设置默认值valueinput,然后分别对想原生input的处理一样,分别监听binding.expression的变化和modelEvent事件即可。需要注意的是,我们上面的代码直接给_prop做了赋值操作,这实际上是不符合规范的,但是我目前没有找到更好的方法去实现。下面?是完整的源码:上文我们通过封装v-mymodel为各位同学展示了如何封装和使用自定义指令,接下来我把自己在生产实践中使用自定义指令的一些经验分享给大家,通过实例,我相信各位同学能够更深刻的理解如何在在应用中封装自己的指令,提高效率。下面我们定义一个v-permission指令用于全平台的权限控制role:角色控制;currentUser:当前登录人判断;当前用户是否是业务数据中的创建人或者负责人bussinessStatus:业务状态判断;every:与操作;some:或操作;示例代码

//定义权限类型
constpermissionType={
ROLE:'role',
CURRENTUSER:'currentUser',
BUSSINESSSTATUS:'bussinessStatus',
MIX_EVERY:'every',
MIX_SOME:'some'
}
exportdefault{
//只调用一次,指令第一次绑定到元素时调用
bind:function(){
},
//当前vdom插入到真实dom时,因为是对dom的样式操作,在这里操作
inserted:function(el,binding){
letshow=false;
show=processingType(binding.arg,binding.value);
el.style.display=`${免费云主机域名show?'inline-block':'none'}`
},
//所在组件的VNode更新时调用,状态更新后需要更新显示状态
update:function(el,binding){
//避免无效的模板更新
if(binding.value===binding.oldValue)return;
letshow=false;
show=processingType(binding.arg,binding.value);
el.style.display=`${show?'inline-block':'none'}`
},
//指令所在组件的VNode及其子VNode全部更新后
componentUpdated:function(el,binding){
},
unbind:function(){
},
}
//处理不同类型的权限控制
functionprocessingType(type,value){
letvalues=[];
switch(type){
casepermissionType.ROLE:
returnpermissionByRole(value);
casepermissionType.CURRENTUSER:
returnpermissionCreater(value);
casepermissionType.BUSSINESSSTATUS:
returnpermissionBusinessStatus(value);
casepermissionType.MIX_EVERY:
for(lettypeinvalue){
values.push(processingType(type,value[type]))
}
returnvalues.every(v=>{
returnv;
})
casepermissionType.MIX_SOME:
for(lettypeinvalue){
values.push(processingType(type,value[type]))
}
returnvalues.some(v=>{
returnv;
})
default:
returnfalse;
}
}
//业务状态判断
functionpermissionBusinessStatus(bindingValue){
returnbindingValue.status==bindingValue.value;
}
//当前用户?
functionpermissionCreater(bindingValue){
constuserInfo=JSON.parse(sessionStorage.CDTPcookie);
//console.log(userInfo.userInfo.id,bindingValue)
if(bindingValueinstanceofArray){
returnbindingValue.some(v=>{
returnuserInfo.userInfo.id==v;
})
}
returnuserInfo.userInfo.id==bindingValue;
}
//角色控制
exportfunctionpermissionByRole(bindingValue){
//这里也可以是store里的用户信息
constuserInfo=JSON.parse(sessionStorage.userInfo);
letroles=[]
if(userInfo){
roles=userInfo.roleList
}
letshow=false;
if(bindingValueinstanceofArray){
returnroles.some(role=>{//多角色处理
returnbindingValue.some(item=>{
returnrole.roleCode===item
})
})
}elseif(typeofbindingValue=='string'){
show=roles.some(role=>{
returnrole.roleCode===bindingValue;
})
}
returnshow;
}

简单说一下上面?指令的定义思路和使用方法。整体思路就是通过processingType处理权限逻辑,使用el.style.display控制组件显示或隐藏。我在这里从日常应用中提取了一些通用的processingType中的权限处理方式,方便大家理解也供大家参考。下面逐一说一下权限指令各个类型的使用方法:

//角色权限

//判断当前登录人

//判断业务状态

//角色是leader或者是当前订单的创建者,有权限

//角色是leader并且是当前订单的创建者,有权限

v-input 输入框限制,限制数字、保留n位小数点等。

exportdefault{
inserted:function(el,binding,vnode){
el.addEventListener('input',function(e){
if(binding.arg=='toFixed'){
//限制输入n位小数点
toFiexd(e.target,vnode,binding.value)
}else{
//限制数字输入
Integer(e.target,vnode)
}
})
},
}
functiontoFiexd(target,vnode,v){
console.log(v);
letln=2;
if(v){
ln=v;
}
varregStrs=[
['^0(d+)$','$1'],//禁止录入整数部分两位以上,但首位为0
['[^d.]+$',''],//禁止录入任何非数字和点
['.(d?).+','.$1'],//禁止录入两个以上的点
['^(d+.d{'+ln+'}).+','$1']//禁止录入小数点后两位以上
];
for(vari=0;i

这里需要特别注意的是下面这行代码

vnode.componentInstance.$listeners.input(target.value)

登录后复制

登录后复制我们为什么需要添加这一句呢,我们明明已经为target.value做了赋值。
实际上这一句代码相当于指令作用组件内部的$emit('input',target.value),这是因为如果我们是在antd或者elementui中的输入框组件上添加我们定义的v-input指令,直接为target.value赋值是不能生效的,修改的只是原生input控件value值,并没有修改自定义组件的value,还需要通过触发input事件去同步组件状态,修改value值。(这里不了解为什么需要触发input事件区同步状态的同学了解一下v-model的语法糖原理即可理解,
使用方法:

我们也可以通过自定义指令做对内容到处理,比如空值处理数字千分数逗号分割

exportdefault{
bind:function(){
},
inserted:function(el,binding){
dealContent(el,binding)
},
update:function(el,binding){
dealContent(el,binding)
},
componentUpdated:function(){
},
unbind:function(){
},
}
functiondealContent(el,binding){
const{arg}=binding;
if(arg=='empty'){
if(!el.textContent){//空值显示
el.textContent=binding.value||'暂无数据';
}
}elseif(arg=='money'){//金额千分位逗号分割,如10000000显示为100,000,00
if(binding.value){
el.textContent=dealMoney(binding.value);
}else{
el.textContent=dealMoney(el.textContent);
}
}
}

千分位分割代码:

//金额处理
exportfunctiondealMoney(money,places=2){
constzero=`0.00`;
if(isNaN(money)||money==='')returnzero;
if(money&&money!=null){
money=`${money}`;
letleft=money.split('.')[0];//小数点左边部分
letright=money.split('.')[1];//小数点右边
//保留places位小数点,当长度没有到places时,用0补足。
right=right?(right.length>=places?'.'+right.substr(0,places):'.'+right+'0'.repeat(places-right.length)):('.'+'0'.repeat(places));
vartemp=left.split('').reverse().join('').match(/(d{1,3})/g);//分割反向转为字符串然后最多3个,最少1个,将匹配的值放进数组返回
return(Number(money)

使用方法:

{{message}}

100000

千分位分割代码:
使用方法:
v-preview方便的实现文件预览功能预览图片;预览文件;其他预览类业务功能

import{isOffic,isPdf,isImage}from'@/utils/base'
import{previewWithOffice}from'@/utils/fileUtils.js'
exportdefault{
inserted:function(el,binding){
el.onclick=function(e){
letparams=binding.value
if(isOffic(params.name)){
e.preventDefault()
e.stopPropagation()
previewWithOffice(params.url)//使用office在线预览打开
}elseif(isPdf(params.name)||isImage(params.name)){
e.preventDefault()
e.stopPropagation()
if(params.url){//直接打开url
previewFile(params)
}
}
}
},
//指令所在组件的VNode及其子VNode全部更新后
componentUpdated:function(el,binding){
el.onclick=function(e){
letparams=binding.value
if(isOffic(params.name)){
//使用插件预览Office文件
e.preventDefault()
e.stopPropagation()
previewWithOffice(params.url)
}elseif(isPdf(params.name)||isImage(params.name)){
//预览图片和pdf等能直接打开的文件
e.preventDefault()
e.stopPropagation()
previewFile(params)
}
}
},
unbind(el){
el.onclick=null;
}
}
//预览图片和pdf等能直接打开的文件
functionpreviewFile(params){
leta=document.createElement("a");
a.download=params.name
a.href=params.url;
a.target="_blank";
a.click();
a=null;
}

使用方法:

{{file.name}}

使用方法:
以上就是“Vue中的自定义指令怎么实现”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注百云主机行业资讯频道。

相关推荐: Kotlin构造函数、成员变量和init代码块执行顺序是什么

今天小编给大家分享一下Kotlin构造函数、成员变量和init代码块执行顺序是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。在Kotlin中…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

(0)
打赏 微信扫一扫 微信扫一扫
上一篇 02/23 12:20
下一篇 02/23 12:20

相关推荐