Vue 3自定义指令开发的相关总结


Posted in Vue.js onJanuary 29, 2021

什么是指令(directive)

在Angular和Vue中都有Directive的概念,我们通常讲Directive 翻译为“指令”。

在计算机技术中,指令是由指令集架构定义的单个的CPU操作。在更广泛的意义上,“指令”可以是任何可执行程序的元素的表述,例如字节码。

那么在前端框架Vue中“指令”到底是什么,他有什么作用呢?

 在Vue开发中我们在模板中经常会使用v-model和v-show等以v-开头的关键字,这些关键字就是Vue框架内置的指令。通过使用v-model,可以获取实现DOM和数据的绑定;使用v-show,可以控制DOM元素显示。简而言之通过使用这些模板上的标签,让框架对DOM元素进行了指定的处理,同时DOM改变后框架可以同时更新指定数据。指令是Vue MVVM的基础之一。

指令的使用场景

除了使用内置的指令,Vue同样支持自定义指令,以下场景可以考虑通过自定义指令实现:

DOM的基础操作,当组件中的一些处理无法用现有指令实现,可以自定义指令实现。例如组件水印,自动focus。相对于用ref获取DOM操作,封装指令更加符合MVVM的架构,M和V不直接交互。

<p v-highlight="'yellow'">Highlight this text bright yellow</p>

多组件可用的通用操作,通过使用组件(Component)可以很好的实现复用,同样通过使用组件也可以实现功能在组件上的复用。例如拼写检查、图片懒加载。使用组件,只要在需要拼写检查的输入组件上加上标签,遍可为组件注入拼写检查的功能,无需再针对不同组件封装新的支持拼写功能呢。

Vue 3如何自定义指令

Vue支持全局注册和局部注册指令。

全局注册注册通过app实例的directive方法进行注册。

let app = createApp(App)
app.directive('highlight', {
beforeMount(el, binding, vnode) {
el.style.background = binding.value
}
})

局部注册通过给组件设置directive属性注册

export default defineComponent({
name: "WebDesigner",
components: {
Designer,
},
directives: {
highlight: {
beforeMount(el, binding, vnode) {
el.style.background = binding.value;
},
},
},
});

注册组件包含组件的名字,需要唯一和组件的一个实现对象,组册后即可在任何元素上使用了。

<p v-highlight="'yellow'">Highlight this text bright yellow</p>

自定义组件就是实现Vue提供的钩子函数,在Vue 3中钩子函数的生命周期和组件的生命周期类似: 

  • created - 元素创建后,但是属性和事件还没有生效时调用。
  • beforeMount- 仅调用一次,当指令第一次绑定元素的时候。
  • mounted- 元素被插入父元素时调用.
  • beforeUpdate: 在元素自己更新之前调用
  • Updated - 元素或者子元素更新之后调用.
  • beforeUnmount: 元素卸载前调用.
  • unmounted -当指令卸载后调用,仅调用一次

每一个钩子函数都有如下参数:

  • el: 指令绑定的元素,可以用来直接操作DOM
  • binding: 数据对象,包含以下属性

                  instance: 当前组件的实例,一般推荐指令和组件无关,如果有需要使用组件上下文ViewModel,可以从这里获取
                  value: 指令的值,即上面示例中的“yellow“
                  oldValue: 指令的前一个值,在beforeUpdate和Updated 中,可以和value是相同的内容。
                  arg: 传给指令的参数,例如v-on:click中的click。
                  modifiers: 包含修饰符的对象。例如v-on.stop:click 可以获取到一个{stop:true}的对象

  • vnode: Vue 编译生成的虚拟节点,
  • prevVNode: Update时的上一个虚拟节点

Vue 2 指令升级

指令在Vue3中是一个Breaking Change,指令的钩子函数名称和数量发生了变化。Vue3中为指令创建了更多的函数,函数名称和组件的生命周期一致,更易理解。

以下是变化介绍

Vue 3自定义指令开发的相关总结

另一个变化是组件上下文对象的获取方式发生了变化。一般情况下推荐指令和组件实例相互独立,从自定义指令内部去访问组件实例,那可能说明这里不需要封装指令,指令就是组件本事的功能。但是可能的确有某些场景需要去获取组件实例。

在Vue 2中通过vnode参数获取

bind(el, binding, vnode) {
  const vm = vnode.context
}

在Vue 3中 通过binding参数获取

mounted(el, binding, vnode) {
  const vm = binding.instance
}

Vue 3 自定义指令实例 ? 输入拼写检查

这里使用Plugin的方式注入指令。

新建SpellCheckPlugin.ts,声明插件,在插件的install方法中注入指令

import { App } from 'vue'
 
function SpellCheckMain(app: App, options: any) {
//
}
 
export default {
    install: SpellCheckMain
}

SpellCheckMain方法实现组件以及,拼写检查方法,具体拼写检查规则可以根据业务或者使用其他插件方法实现

function SpellCheckMain(app: App, options: any) {
    const SpellCheckAttribute = "spell-check-el";
 
    let SpellCheckTimer: Map<string, number> = new Map();
    let checkerId = 0;
    function checkElement(el: HTMLElement) {
        let attr = el.getAttribute(SpellCheckAttribute);
        if (attr) {
            clearTimeout(SpellCheckTimer.get(attr));
            let timer = setTimeout(() => { checkElementAsync(el) }, 500);
            SpellCheckTimer.set(attr, timer)
        }
    }
    function checkText(words?: string | null): [string?] {
        if (!words) {
            return [];
        }
        let errorWordList: [string?] = [];
        try {
            let wordsList = words.match(/[a-zA-Z]+/ig);
            wordsList?.forEach((word) => {
                if (!checkWord(word)) {
                    errorWordList.push(word);
                }
            })
        }
        catch {
 
        }
        return errorWordList;
    }
    function checkWord(text: string) {
        //模拟拼写检查,这里使用其他检查库
        return text.length > 6 ? false : true;
    }
    function checkElementAsync(el: HTMLElement) {
 
        let text = (el as HTMLInputElement).value || el.innerText;
        let result = checkText(text);
 
        let attr = el.getAttribute(SpellCheckAttribute);
        if (!attr) {
            return;
        }
 
        if (result && result.length) {
            el.style.background = "pink"
            let div = document.getElementById(attr);
            if (!div) {
                div = document.createElement("div");
                div.id = attr;
                div.style.position = "absolute"
                div.style.top = "0px"
                div.style.left = el.clientWidth + "px"
 
                if (el.parentElement) {
                    el.parentElement.style.position = "relative"
                    if (el.parentElement.lastChild === el) {
                        el.parentElement.appendChild(div);
                    }
                    else {
                        el.parentElement.insertBefore(div, el.nextSibling);
                    }
                }
            }
            div.innerHTML = result.length.toString() + " - " + result.join(",");
        } else {
            el.style.background = "";
 
            let div = document.getElementById(attr);
            if (div) {
                div.innerHTML = ""
            }
        }
 
        console.log(result)
    }
 
    app.directive('spell-check', {
        created() {
            console.log("created", arguments)
        },
        mounted: function (el, binding, vnode, oldVnode) {
 
            console.log("mounted", arguments)
            //set checker id for parent
            let attr = "spellcheck-" + (checkerId++);
            el.setAttribute(SpellCheckAttribute, attr);
            console.log("attr", attr)
 
            if (el.tagName.toUpperCase() === "DIV") {
                el.addEventListener("blur", function () {
                    checkElement(el)
                }, false);
            }
            if (el.tagName.toUpperCase() === "INPUT") {
                el.addEventListener("keyup", function () {
                    checkElement(el)
                }, false);
            }
            // el.addEventListener("focus", function () {
            //     checkElement(el)
            // }, false);
        },
        updated: function (el) {
            console.log("componentUpdated", arguments)
            checkElement(el);
        },
        unmounted: function (el) {
            console.log("unmounted", arguments)
 
            let attr = el.getAttribute(SpellCheckAttribute);
            if (attr) {
                let div = document.getElementById(attr);
                if (div) {
                    div.remove();
                }
            }
        }
    })
}

main.ts中使用插件

/// <reference path="./vue-app.d.ts" />
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import SpellCheckPlugin  from './plugins/SpellCheckPlugin'
 
let app = createApp(App)
app.use(SpellCheckPlugin)
app.use(router).mount('#app')

组件中直接使用指令即可

<template>
  <div ref="ssHost" style="width: 100%; height: 600px"></div>
  <div><div ref="fbHost" spell-check v-spell-check="true" contenteditable="true" spellcheck="false" style="border: 1px solid #808080;width:600px;"></div></div>
  <div><input v-model="value1" v-spell-check spellcheck="false" style="width:200px;" /></div>
</template>

结合在使用SpreadJS上 ,基于检查用户拼写输入的功能,效果如下图:

Vue 3自定义指令开发的相关总结

以上就是Vue 3自定义指令开发的相关总结的详细内容,更多关于Vue 3自定义指令开发的资料请关注三水点靠木其它相关文章!

Vue.js 相关文章推荐
Vue如何循环提取对象数组中的值
Nov 18 Vue.js
Vue3+elementui plus创建项目的方法
Dec 01 Vue.js
如何正确解决VuePress本地访问出现资源报错404的问题
Dec 03 Vue.js
vue实现滚动鼠标滚轮切换页面
Dec 13 Vue.js
vue3.0实现插件封装
Dec 14 Vue.js
vue使用过滤器格式化日期
Jan 20 Vue.js
Vue中使用wangeditor富文本编辑的问题
Feb 07 Vue.js
如何在 Vue 中使用 JSX
Feb 14 Vue.js
vue中axios封装使用的完整教程
Mar 03 Vue.js
vue点击弹窗自动触发点击事件的解决办法(模拟场景)
May 25 Vue.js
在vue中import()语法不能传入变量的问题及解决
Apr 01 Vue.js
vue postcss-px2rem 自适应布局
May 15 Vue.js
vue 中this.$set 动态绑定数据的案例讲解
Jan 29 #Vue.js
vue穿梭框实现上下移动
Jan 29 #Vue.js
vue使用transition组件动画效果的实例代码
Jan 28 #Vue.js
Vue ​v-model相关知识总结
Jan 28 #Vue.js
Vue 数据响应式相关总结
Jan 28 #Vue.js
vue.js实现点击图标放大离开时缩小的代码
Jan 27 #Vue.js
vscode自定义vue模板的实现
Jan 27 #Vue.js
You might like
PHP图片上传类带图片显示
2006/11/25 PHP
php 将bmp图片转为jpg等其他任意格式的图片
2009/06/21 PHP
PHP中常见的缓存技术实例分析
2015/09/23 PHP
Firefox outerHTML实现代码
2009/06/04 Javascript
js动态添加onload、onresize、onscroll事件(另类方法)
2012/12/26 Javascript
javascript浏览器窗口之间传递数据的方法
2015/01/20 Javascript
javascript获取select值的方法分析
2015/07/02 Javascript
JavaScript统计字符串中每个字符出现次数完整实例
2016/01/28 Javascript
canvas 弹幕效果(实例分享)
2017/01/11 Javascript
从零开始搭建一个react项目开发
2018/02/09 Javascript
小试SVG之新手小白入门教程
2019/01/08 Javascript
原生JS实现图片懒加载之页面性能优化
2019/04/26 Javascript
微信小程序云开发修改云数据库中的数据方法
2019/05/18 Javascript
微信小程序实现签字功能
2019/12/23 Javascript
微信小程序中使用 async/await的方法实例分析
2020/05/06 Javascript
[01:35:53]完美世界DOTA2联赛PWL S3 Magma vs GXR 第二场 12.13
2020/12/17 DOTA
Python装饰器基础详解
2016/03/09 Python
python编程实现归并排序
2017/04/14 Python
Python排序搜索基本算法之插入排序实例分析
2017/12/11 Python
Django 反向生成url实例详解
2019/07/30 Python
Django项目之Elasticsearch搜索引擎的实例
2019/08/21 Python
python3.6生成器yield用法实例分析
2019/08/23 Python
python如何通过twisted搭建socket服务
2020/02/03 Python
Pandas将列表(List)转换为数据框(Dataframe)
2020/04/24 Python
python 如何将office文件转换为PDF
2020/09/22 Python
使用py-spy解决scrapy卡死的问题方法
2020/09/29 Python
GAP美国官网:美国休闲时尚品牌
2016/08/26 全球购物
外企测试工程师面试题
2015/02/01 面试题
车间班组长的职责
2013/12/13 职场文书
访谈节目策划方案
2014/05/15 职场文书
烹饪大赛策划方案
2014/05/26 职场文书
党的群众路线教育实践活动个人剖析材料
2014/10/07 职场文书
教师学习群众路线心得体会
2014/11/04 职场文书
病人慰问信范文
2015/02/15 职场文书
保外就医申请书范文
2015/08/06 职场文书
python可视化大屏库big_screen示例详解
2021/11/23 Python