Rumah  >  Artikel  >  hujung hadapan web  >  Ketahui lebih lanjut tentang arahan tersuai dalam Vue

Ketahui lebih lanjut tentang arahan tersuai dalam Vue

青灯夜游
青灯夜游ke hadapan
2022-11-21 20:20:141450semak imbas

Ketahui lebih lanjut tentang arahan tersuai dalam Vue

Penyediaan: Pengenalan kepada arahan tersuai

Selain arahan terbina dalam lalai untuk fungsi teras (v-model dan v-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.

Percubaan: Laksanakan v-mymodel

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.

Arahan definisi

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

. Jadi kita keluarkan yang lain dahulu, dan kodnya menjadi seperti ini.
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

ialah arahan kami sendiri, termasuk ,
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. valueexpression Seterusnya kita perlu melakukan dua perkara: argvnodevnode mengikat acara oldVnode dan menyegerakkan nilai vnode

kepada luaran

    Pengikatan nilai, pantau perubahan dalam
  • dan kemas kini kepada input inputvalue
  • Ini lebih mudah dilaksanakan untuk value komponen asli: valueinputvalue Biar saya terangkan kod di atas di sini Apakah itu
  • Ini adalah konteks komponen di mana arahan kami berada . Pelajar yang tidak biasa dengan struktur
dinasihatkan untuk membaca dokumen rasmi terlebih dahulu Namun, penerangan dalam dokumen agak mudah dan tidak begitu menyeluruh, jadi sebaiknya

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

untuk menambah mendengar acara ; maka kita perlu Untuk melakukan perkara kedua, ikat nilai

, 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=&#39;message&#39;/>

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

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

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

Vue.directive(&#39;mymodel&#39;, {
        //只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
        bind(el, binding, vnode, oldVnode) {
           //原生input组件的处理
           if(vnode.tag===&#39;input&#39;){
                //第一步,添加inout事件监听
                el.addEventListener(&#39;input&#39;, (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: &#39;value&#39;,
        event: &#39;input&#39;
  }
}
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需要同学去多了解一下VnodeComponentAPI,就像之前说的,最简单的方法就是直接在控制台中直接打印出vnode对象,组件的vnode上有Component的实例componentInstance

接下来简单说一下上面的代码,首先我们可以在componentOptions.Ctor.extendOptions上找到model的定义,如果没有的话需要设置默认值valueinput,然后分别对想原生input的处理一样,分别监听binding.expression的变化和modelEvent事件即可。

需要注意的是,我们上面的代码直接给_prop做了赋值操作,这实际上是不符合规范的,但是我目前没有找到更好的方法去实现,有好思路的同学可以在评论区留言指教。

下面?是完整的源码:

应用实践:4个实用的自定义指令

上文我们通过封装v-mymodel为各位同学展示了如何封装和使用自定义指令,接下来我把自己在生产实践中使用自定义指令的一些经验分享给大家,通过实例,我相信各位同学能够更深刻的理解如何在在应用中封装自己的指令,提高效率。

权限控制

下面我们定义一个v-permission指令用于全平台的权限控制

  • role:角色控制;
  • currentUser:当前登录人判断;当前用户是否是业务数据中的创建人或者负责人
  • bussinessStatus:业务状态判断;
  • every:与操作;
  • some:或操作;

示例代码

//定义权限类型
const permissionType = {
    ROLE: &#39;role&#39;,
    CURRENTUSER:&#39;currentUser&#39;,
    BUSSINESSSTATUS: &#39;bussinessStatus&#39;,
    MIX_EVERY: &#39;every&#39;,
    MIX_SOME: &#39;some&#39;
}
export default {
    //只调用一次,指令第一次绑定到元素时调用
    bind: function () {
    },
    //当前vdom插入到真实dom时,因为是对dom的样式操作,在这里操作
    inserted: function (el, binding) {
        let show = false;
        show=processingType(binding.arg,binding.value); 
        el.style.display = `${show ? &#39;inline-block&#39; : &#39;none&#39;}`
    },
    //所在组件的VNode更新时调用,状态更新后需要更新显示状态
    update: function (el, binding) {
        //避免无效的模板更新
        if(binding.value===binding.oldValue) return;
        let show = false;
        show=processingType(binding.arg,binding.value); 
        el.style.display = `${show ? &#39;inline-block&#39; : &#39;none&#39;}`
    },
    //指令所在组件的 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 == &#39;string&#39;) {
        show = roles.some(role => {
            return role.roleCode === bindingValue;
        })
    }
    return show;
}

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

下面逐一说一下权限指令各个类型的使用方法:

//角色权限
<component v-permission:role=&#39;leader&#39;></component>
//判断当前登录人
<component v-permission:currentUser=&#39;orderInfo.createUser&#39;></component>
//判断业务状态
<component v-permission:bussinessStatus=&#39;{status:orderStatus.RUNNING,value:orderInfo.status}&#39;></component>
//角色是leader或者是当前订单的创建者,有权限
<component v-permission:some="{role:&#39;leader&#39;,currentUser:&#39;orderInfo.createUser&#39;}"></component>
//角色是leader并且是当前订单的创建者,有权限
<component v-permission:every="{role:&#39;leader&#39;,currentUser:&#39;orderInfo.createUser&#39;}"></component>

输入限制

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

export default {
    inserted: function (el, binding, vnode) {
        el.addEventListener(&#39;input&#39;, function (e) {
            if (binding.arg == &#39;toFixed&#39;) {
                //限制输入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 = [
        [&#39;^0(\\d+)$&#39;, &#39;$1&#39;], //禁止录入整数部分两位以上,但首位为0
        [&#39;[^\\d\\.]+$&#39;, &#39;&#39;], //禁止录入任何非数字和点
        [&#39;\\.(\\d?)\\.+&#39;, &#39;.$1&#39;], //禁止录入两个以上的点
        [&#39;^(\\d+\\.\\d{&#39; + ln + &#39;}).+&#39;, &#39;$1&#39;] //禁止录入小数点后两位以上
    ];
    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==&#39;empty&#39;){
       if(!el.textContent){//空值显示
            el.textContent=binding.value||&#39;暂无数据&#39;;
        }
   }else if(arg==&#39;money&#39;){//金额千分位逗号分割,如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 === &#39;&#39;) return zero;
    if (money && money != null) {
        money = `${money}`;
        let left = money.split(&#39;.&#39;)[0]; // 小数点左边部分
        let right = money.split(&#39;.&#39;)[1]; // 小数点右边
        // 保留places位小数点,当长度没有到places时,用0补足。
        right = right ? (right.length >= places ? &#39;.&#39; + right.substr(0, places) : &#39;.&#39; + right + &#39;0&#39;.repeat(places - right.length)) : (&#39;.&#39; + &#39;0&#39;.repeat(places));
        var temp = left.split(&#39;&#39;).reverse().join(&#39;&#39;).match(/(\d{1,3})/g); // 分割反向转为字符串然后最多3个,最少1个,将匹配的值放进数组返回
        return (Number(money) < 0 ? &#39;-&#39; : &#39;&#39;) + temp.join(&#39;,&#39;).split(&#39;&#39;).reverse().join(&#39;&#39;) + right; // 补齐正负号和货币符号,数组转为字符串,通过逗号分隔,再分割(包含逗号也分割)反向转为字符串变回原来的顺序
    } else if (money === 0) {
        return zero;
    } else {
        return zero;
    }
}

使用方法:

<span v-content:empty="&#39;无&#39;">{{message}}</span>
<!-- 金额千分位逗号分割 -->
<span v-content:money>100000</span>

文件预览

v-preview方便的实现文件预览功能

  • 预览图片;

  • 预览文件;

  • 其他预览类业务功能

import {isOffic,isPdf,isImage} from &#39;@/utils/base&#39;
import {previewWithOffice} from &#39;@/utils/fileUtils.js&#39;
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=&#39;url&#39; 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
  • 通用的自定义指令使用技巧

(学习视频分享:web前端开发编程基础视频

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!

Kenyataan:
Artikel ini dikembalikan pada:juejin.cn. Jika ada pelanggaran, sila hubungi admin@php.cn Padam