Selain arahan terbina dalam lalai untuk fungsi teras (
v-model
danv-show
, dsb.), Vue juga membenarkan pendaftaran arahan tersuai. Ambil perhatian bahawa dalam Vue2.0, bentuk utama penggunaan semula dan pengabstrakan kod ialah komponen. Walau bagaimanapun, dalam beberapa kes, anda masih perlu melakukan operasi peringkat rendah pada elemen DOM biasa, dalam hal ini arahan tersuai digunakan. [Belajar perkongsian video: tutorial video vue, video bahagian hadapan web]
Sebagai pembangun yang menggunakan Vue
, kami pasti tentang Vue
perintah Tidak asing, seperti v-model
, v-on
, v-for
, v-if
, dsb. Pada masa yang sama, Vue juga menyediakan pembangun dengan API untuk arahan tersuai yang mahir boleh bertambah baik keupayaan kami untuk menulis kod. Kecekapan itu membolehkan kami menjimatkan masa dan memancing dengan gembira~
Saya percaya ramai pelajar sudah tahu tentang arahan tersuai Vue tidak akan dibincangkan secara terperinci di sini Ya, dokumentasi rasmi sangat terperinci. Tetapi saya tidak tahu sama ada anda pelajar berasa seperti ini teknologi ini berasa sangat mudah dan tidak sukar. Saya juga merasakan bahawa saya telah mempelajarinya, tetapi saya tidak tahu bagaimana untuk menerapkannya. Dokumen ini ditulis untuk menyelesaikan masalah beberapa pelajar ini.
PS: Untuk arahan tersuai yang akan kami bincangkan kali ini, kami menggunakan kaedah penulisan vue2.x
, tetapi vue3.x
hanyalah beberapa perubahan dalam fungsi cangkuk seperti yang anda fahami setiap fungsi cangkuk Maksud , tidak terdapat banyak perbezaan dalam penggunaan antara kedua-duanya.
Artikel terakhir saya menyebut bahawa saya perlu melaksanakan arahan v-model
sendiri, di sini saya menggunakan simulasi v-myodel
Versi ringkas, dengan cara ini, untuk membiasakan pelajar yang tidak dikenali dengan langkah dan langkah berjaga-jaga arahan tersuai.
Selesaikan dahulu idea: pelaksanaan kawalan dan komponen input
asli perlu dibezakan Pelaksanaan input
adalah agak mudah , mari kita laksanakan dahuluinput
pemprosesan.
Mula-mula, kita mula-mula mentakrifkan arahan yang tidak melakukan apa-apa Mengikat, jadi kita boleh mentakrifkannya dalam fungsi cangkuk
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
Mari kita bincangkan secara ringkas tentang beberapa parameter panggil balik fungsi value
ialah bind
yang sepadan dengan komponen pengikat arahan dan
Vue.directive('mymodel', { //只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 bind(el, binding, vnode, oldVnode) { } })
, bind
, dsb., el
ialah nod dom
yang sepadan dengan komponen terikat semasa dan binding
ialah keadaan sebelum name
dikemas kini. value
expression
Seterusnya kita perlu melakukan dua perkara: arg
vnode
vnode
mengikat acara oldVnode
dan menyegerakkan nilai vnode
input
input
value
value
komponen asli: value
input
value
Biar saya terangkan kod di atas di sini Apakah itu melihat objek input
. dalam konsol untuk melihat struktur khususnya Ia sangat membantu untuk kami merangkum arahan tersuai dan juga membantu untuk memahami prinsip
//第一步,添加inout事件监听 el.addEventListener('input', (e) => { //context是input所在的父组件,这一步是同步数据 vnode.context[binding.expression] = e.target.value; }) //监听绑定的变量 vnode.context.$watch(binding.expression, (v) => { el.value = v; })
Kita boleh mendapatkan nilai terikat pada v-model melalui vnode.context
, dan kita juga boleh mengubah suainya. Dalam kod di atas, kami mula-mula menyegerakkan nilai vnode
log
ke luar (vnode
) dengan mengendalikan Vue
dalam acara input tambahan, yang mempunyai kesan yang sama seperti menggunakan
, dengar perubahan dalam context[binding.expression]
dan selaraskan perubahan nilai kepada vnode.context[binding.expression] = e.target.value
kami fikir kami boleh menggunakan kaedah input
pada contoh Vue. Nilai monitor berubah, dan value
ialah contoh context
, dan @input
ialah atribut yang ingin kita pantau jika kita menulis value
seperti ini
参考vue实战视频讲解:进入学习
<input v-mymodel='message'/>
那么binding.expression
就是字符串'message'
。所以我们想下面的代码这样监听绑定的响应式数据。
//监听绑定的变量 vnode.context.$watch(binding.expression, (v) => { el.value = v; })
至此,input
的v-mymodel
的处理就完成了(当然input
组件还有type
为checkbox
,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' } } let modelValue = componentOptions.Ctor.extendOptions.model.value; let modelEvent = 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
更加复杂一点,是结合自定义指令、模板编译等去实现的,因为我们是应用级别的封装,所以采用了上述的方式实现。
实现此v-mymodel
需要同学去多了解一下Vnode
和Component
的API
,就像之前说的,最简单的方法就是直接在控制台中直接打印出vnode
对象,组件的vnode
上有Component
的实例componentInstance
。
接下来简单说一下上面的代码,首先我们可以在componentOptions.Ctor.extendOptions
上找到model
的定义,如果没有的话需要设置默认值value
和input
,然后分别对想原生input
的处理一样,分别监听binding.expression
的变化和modelEvent
事件即可。
需要注意的是,我们上面的代码直接给_prop
做了赋值操作,这实际上是不符合规范的,但是我目前没有找到更好的方法去实现,有好思路的同学可以在评论区留言指教。
下面?是完整的源码:
上文我们通过封装v-mymodel
为各位同学展示了如何封装和使用自定义指令,接下来我把自己在生产实践中使用自定义指令的一些经验分享给大家,通过实例,我相信各位同学能够更深刻的理解如何在在应用中封装自己的指令,提高效率。
下面我们定义一个v-permission
指令用于全平台的权限控制
示例代码
//定义权限类型 const permissionType = { ROLE: 'role', CURRENTUSER:'currentUser', BUSSINESSSTATUS: 'bussinessStatus', MIX_EVERY: 'every', MIX_SOME: 'some' } export default { //只调用一次,指令第一次绑定到元素时调用 bind: function () { }, //当前vdom插入到真实dom时,因为是对dom的样式操作,在这里操作 inserted: function (el, binding) { let show = 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; let show = false; show=processingType(binding.arg,binding.value); el.style.display = `${show ? 'inline-block' : 'none'}` }, //指令所在组件的 VNode 及其子 VNode 全部更新后 componentUpdated: function (el, binding) { }, unbind: function () { }, } //处理不同类型的权限控制 function processingType(type,value){ let values=[]; switch (type) { case permissionType.ROLE: return permissionByRole(value); case permissionType.CURRENTUSER: return permissionCreater(value); case permissionType.BUSSINESSSTATUS: return permissionBusinessStatus(value); case permissionType.MIX_EVERY: for(let type in value){ values.push(processingType(type,value[type])) } return values.every(v=>{ return v; }) case permissionType.MIX_SOME: for(let type in value){ values.push(processingType(type,value[type])) } return values.some(v=>{ return v; }) default: return false; } } //业务状态判断 function permissionBusinessStatus(bindingValue){ return bindingValue.status==bindingValue.value; } //当前用户? function permissionCreater(bindingValue){ const userInfo = JSON.parse(sessionStorage.CDTPcookie); // console.log(userInfo.userInfo.id,bindingValue) if(bindingValue instanceof Array){ return bindingValue.some(v=>{ return userInfo.userInfo.id==v; }) } return userInfo.userInfo.id==bindingValue; } //角色控制 export function permissionByRole(bindingValue) { //这里也可以是store里的用户信息 const userInfo = JSON.parse(sessionStorage.userInfo); let roles = [] if (userInfo) { roles = userInfo.roleList } let show = false; if (bindingValue instanceof Array) { return roles.some(role => {//多角色处理 return bindingValue.some(item => { return role.roleCode === item }) }) } else if (typeof bindingValue == 'string') { show = roles.some(role => { return role.roleCode === bindingValue; }) } return show; }
简单说一下上面?指令的定义思路和使用方法。整体思路就是通过processingType处理权限逻辑,使用el.style.display控制组件显示或隐藏。我在这里从日常应用中提取了一些通用的processingType中的权限处理方式,方便大家理解也供大家参考。
下面逐一说一下权限指令各个类型的使用方法:
//角色权限 <component v-permission:role='leader'></component> //判断当前登录人 <component v-permission:currentUser='orderInfo.createUser'></component> //判断业务状态 <component v-permission:bussinessStatus='{status:orderStatus.RUNNING,value:orderInfo.status}'></component> //角色是leader或者是当前订单的创建者,有权限 <component v-permission:some="{role:'leader',currentUser:'orderInfo.createUser'}"></component> //角色是leader并且是当前订单的创建者,有权限 <component v-permission:every="{role:'leader',currentUser:'orderInfo.createUser'}"></component>
v-input 输入框限制,限制数字、保留n位小数点等。
export default { 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) } }) }, } function toFiexd(target, vnode, v) { console.log(v); let ln = 2; if (v) { ln = v; } var regStrs = [ ['^0(\\d+)$', '$1'], //禁止录入整数部分两位以上,但首位为0 ['[^\\d\\.]+$', ''], //禁止录入任何非数字和点 ['\\.(\\d?)\\.+', '.$1'], //禁止录入两个以上的点 ['^(\\d+\\.\\d{' + ln + '}).+', '$1'] //禁止录入小数点后两位以上 ]; for (var i = 0; i < regStrs.length; i++) { var reg = new RegExp(regStrs[i][0]); target.value = target.value.replace(reg, regStrs[i][1]); } //对于封装的像el-input组件,因为其需要通过input事件同步状态 if(vnode.componentInstance){ vnode.componentInstance.$listeners.input(target.value) } } function Integer(target, vnode) { let valueStr = target.value if (valueStr.length == 1) { //第一个数字不为0 valueStr = valueStr.replace(/[^0-9]/g, ""); } else { //只能输入正整数 valueStr = valueStr.replace(/\D/g, ""); } target.value = valueStr; if(vnode.componentInstance){ vnode.componentInstance.$listeners.input(target.value) } }
这里需要特别注意的是下面这行代码
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的语法糖原理即可理解,
使用方法:
<!-- 限制输入两位小数数字 --> <input v-input:toFixed="2"/> <!-- 限制输入正整数 --> <el-input v-input:integer/>
我们也可以通过自定义指令做对内容到处理,比如
空值处理
数字千分数逗号分割
export default { bind:function(){ }, inserted:function(el,binding){ dealContent(el,binding) }, update:function(el,binding){ dealContent(el,binding) }, componentUpdated:function(){ }, unbind:function(){ }, } function dealContent(el,binding){ const {arg}=binding; if(arg=='empty'){ if(!el.textContent){//空值显示 el.textContent=binding.value||'暂无数据'; } }else if(arg=='money'){//金额千分位逗号分割,如10000000显示为100,000,00 if (binding.value) { el.textContent = dealMoney(binding.value); }else { el.textContent = dealMoney(el.textContent); } } }
千分位分割代码:
//金额处理 export function dealMoney(money, places = 2) { const zero = `0.00`; if (isNaN(money) || money === '') return zero; if (money && money != null) { money = `${money}`; let left = money.split('.')[0]; // 小数点左边部分 let right = money.split('.')[1]; // 小数点右边 // 保留places位小数点,当长度没有到places时,用0补足。 right = right ? (right.length >= places ? '.' + right.substr(0, places) : '.' + right + '0'.repeat(places - right.length)) : ('.' + '0'.repeat(places)); var temp = left.split('').reverse().join('').match(/(\d{1,3})/g); // 分割反向转为字符串然后最多3个,最少1个,将匹配的值放进数组返回 return (Number(money) < 0 ? '-' : '') + temp.join(',').split('').reverse().join('') + right; // 补齐正负号和货币符号,数组转为字符串,通过逗号分隔,再分割(包含逗号也分割)反向转为字符串变回原来的顺序 } else if (money === 0) { return zero; } else { return zero; } }
使用方法:
<span v-content:empty="'无'">{{message}}</span> <!-- 金额千分位逗号分割 --> <span v-content:money>100000</span>
v-preview方便的实现文件预览功能
预览图片;
预览文件;
其他预览类业务功能
import {isOffic,isPdf,isImage} from '@/utils/base' import {previewWithOffice} from '@/utils/fileUtils.js' export default { inserted:function(el,binding){ el.onclick=function(e){ let params = binding.value if(isOffic(params.name)){ e.preventDefault() e.stopPropagation() previewWithOffice(params.url)//使用office在线预览打开 }else if(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){ let params = binding.value if(isOffic(params.name)){ //使用插件预览Office文件 e.preventDefault() e.stopPropagation() previewWithOffice(params.url) }else if(isPdf(params.name) || isImage(params.name)){ //预览图片和pdf等能直接打开的文件 e.preventDefault() e.stopPropagation() previewFile(params) } } }, unbind(el){ el.onclick=null; } } //预览图片和pdf等能直接打开的文件 function previewFile(params) { let a = document.createElement("a"); a.download = params.name a.href = params.url; a.target = "_blank"; a.click(); a = null; }
使用方法:
<!-- 预览图片 --> <image :src='url' v-preview="{name:file.name,url:file.url}"></image> <!-- 预览文件 --> <span v-preview="{name:file.name,url:file.url}">{{file.name}}</span>
各位同学可以试着自己实现一个v-loading
的加载中的指令,通过设置一个bool
值来设置容器的加载状态。
如有疑问可以在评论区留言。
本文主要讲了如下几件事:
vue
自定义指令介绍v-model
Atas ialah kandungan terperinci Ketahui lebih lanjut tentang arahan tersuai dalam Vue. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!