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自定义插件封装,实现简易的elementUi的Message和MessageBox的示例
Nov 20 Vue.js
vue组件中节流函数的失效的原因和解决方法
Dec 02 Vue.js
vue中如何自定义右键菜单详解
Dec 08 Vue.js
vue实现滚动鼠标滚轮切换页面
Dec 13 Vue.js
vue 数据操作相关总结
Dec 17 Vue.js
Vue实现多页签组件
Jan 14 Vue.js
vscode自定义vue模板的实现
Jan 27 Vue.js
vue项目配置 webpack-obfuscator 进行代码加密混淆的实现
Feb 26 Vue.js
vue.js Router中嵌套路由的实用示例
Jun 27 Vue.js
idea编译器vue缩进报错问题场景分析
Jul 04 Vue.js
vue3.0 数字翻牌组件的使用方法详解
Apr 20 Vue.js
vue.js 使用原生js实现轮播图
Apr 26 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
ThinkPHP实现将SESSION存入MYSQL的方法
2014/07/22 PHP
ThinkPHP处理Ajax返回的方法
2014/11/22 PHP
PHP翻页跳转功能实现方法
2020/11/30 PHP
PHP实现的简单组词算法示例
2018/04/10 PHP
YII2.0框架行为(Behavior)深入详解
2019/07/26 PHP
php设计模式之组合模式实例详解【星际争霸游戏案例】
2020/03/27 PHP
JavaScript操作XML实例代码(获取新闻标题并分页,并分页)
2010/05/25 Javascript
提升你网站水平的jQuery插件集合推荐
2011/04/19 Javascript
js计算精度问题小结
2013/04/22 Javascript
一个简单的JS鼠标悬停特效具体方法
2013/06/17 Javascript
js快速排序的实现代码
2013/12/08 Javascript
JQuery的ON()方法支持的所有事件罗列
2015/02/28 Javascript
Js实现无刷新删除内容
2015/04/29 Javascript
JavaScript运动减速效果实例分析
2015/08/04 Javascript
jquery中表单 多选框的一种巧妙写法
2015/09/06 Javascript
Javascript实现通过选择周数显示开始日和结束日的实现代码
2016/05/30 Javascript
判断横屏竖屏(三种)
2017/02/13 Javascript
jQuery上传多张图片带进度条样式(DEMO)
2017/03/02 Javascript
JS获取鼠标位置距浏览器窗口距离的方法示例
2017/04/11 Javascript
xmlplus组件设计系列之选项卡(Tabbar)(5)
2017/05/03 Javascript
深入浅析AngularJs模版与v-bind
2018/07/06 Javascript
vue观察模式浅析
2018/09/25 Javascript
详解如何使用nvm管理Node.js多版本
2019/05/06 Javascript
js实现金山打字通小游戏
2020/07/24 Javascript
Python3获取拉勾网招聘信息的方法实例
2019/04/03 Python
在pytorch中查看可训练参数的例子
2019/08/18 Python
django中瀑布流写法实例代码
2019/10/14 Python
深入分析python 排序
2020/08/24 Python
html5跨域通讯之postMessage的用法总结
2013/11/07 HTML / CSS
Gina Bacconi官网:吉娜贝康尼连衣裙和礼服
2018/04/24 全球购物
联想韩国官网:Lenovo Korea
2018/05/10 全球购物
污水厂厂长岗位职责
2014/01/04 职场文书
应届毕业生个人求职自荐信
2014/01/06 职场文书
求职简历的自我评价
2014/01/31 职场文书
Spring Data JPA的Audit功能审计数据库的变更
2021/06/26 Java/Android
PostGIS的安装与入门使用指南
2022/01/18 PostgreSQL