精华内容
下载资源
问答
  • Vue 3自定义指令开发

    2021-01-28 11:00:15
    在Angular和Vue中都有Directive的概念,我们通常讲Directive 翻译为“指令”。 在计算机技术中,指令是由指令集架构定义的单个的CPU操作。在更广泛的意义上,“指令”可以是任何可执行程序的元素的表述,例如字节码...

    本文由葡萄城技术团队原创并首发

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。

     

    什么是指令(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不直接交互。

    1

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

     

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

     

    Vue 3如何自定义指令

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

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

    1

    2

    3

    4

    5

    6

    let app = createApp(App)

    app.directive('highlight', {

    beforeMount(el, binding, vnode) {

    el.style.background = binding.value

    }

    })

     

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

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    export default defineComponent({

    name: "WebDesigner",

    components: {

    Designer,

    },

    directives: {

    highlight: {

    beforeMount(el, binding, vnode) {

    el.style.background = binding.value;

    },

    },

    },

    });

     

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

    1

    <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 2中通过vnode参数获取

    1

    2

    3

    bind(el, binding, vnode) {

      const vm = vnode.context

    }

     

    在Vue 3中 通过binding参数获取 

    1

    2

    3

    mounted(el, binding, vnode) {

      const vm = binding.instance

    }

     

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

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

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

    1

    2

    3

    4

    5

    6

    7

    8

    9

    import { App } from 'vue'

     

    function SpellCheckMain(app: App, options: any) {

    //

    }

     

    export default {

        install: SpellCheckMain

    }

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

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

    58

    59

    60

    61

    62

    63

    64

    65

    66

    67

    68

    69

    70

    71

    72

    73

    74

    75

    76

    77

    78

    79

    80

    81

    82

    83

    84

    85

    86

    87

    88

    89

    90

    91

    92

    93

    94

    95

    96

    97

    98

    99

    100

    101

    102

    103

    104

    105

    106

    107

    108

    109

    110

    111

    112

    113

    114

    115

    116

    117

    118

    119

    120

    121

    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中使用插件

    1

    2

    3

    4

    5

    6

    7

    8

    9

    /// <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')

     

    组件中直接使用指令即可

    1

    2

    3

    4

    5

    <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上 ,基于检查用户拼写输入的功能,效果如下图:

    以上就是Vue3 自定义指令开发的部分玩法介绍,大家如果知道更多的使用方法欢迎通过留言分享出来。

    展开全文
  • Vue2.0自定义指令的钩子函数 bind: function(el: any, binding: any): void { console.log("bind"); //指令第一次绑定到元素时调用,只执行一次。在这里可以进行一次性的初始化设置。 }, inserted: function(el...

    Vue2.0自定义指令的钩子函数

           bind: function(el: any, binding: any): void {
             console.log("bind");
          //指令第一次绑定到元素时调用,只执行一次。在这里可以进行一次性的初始化设置。
           },
           inserted: function(el: any, binding: any): void {
             console.log("inserted");
           //被绑定的元素,插入到父节点的 DOM 中时调用(仅保证父节点存在)。
           },
           updated: function(el: any, binding: any): void {
             console.log("updated");
           //组件更新时调用。
           },
           componentUpdated: function(el: any, binding: any): void {
             console.log("componentUpdated");
           //组件与子组件更新时调用。
           },
           unbind: function(el: any, binding: any): void {
             console.log("unbind");
          //指令与元素解绑时调用,只执行一次。
           },
    
    Vue2.0全局和局部指令
    // 全局
    Vue.directive('bgcolor', function (el, binding) {
          el.style.backgroundColor = binding.value
    })
    
    // 局部
    directives: {
        bgcolor: (el, binding) => {
            el.style.backgroundColor = binding.value  
        }
    }
    

    Vue指令基本使用

    获取指令参数

    1.通过指令的绑定,可以获取到页面传递的参数,进而对绑定的DOM进行相应处理。

    <el-menu-item index="1" v-demo="activeIndex">处理中心</el-menu-item>
    

    2.binding.value获取传递参数值

          beforeMount(el: any, binding: any) {
            console.log(66666666, binding.value);
          },
    
    操作DOM

    通过掌控el参数即可对绑定元素进行DOM操作

       el.style.backgroundColor = binding.value  
    

    当然了,也可以配合binging来控制DOM样式等等

    <div v-demo='{color: 'orange'}'></div>
    
    export default class A {
      constructor() {}
      demo(app: any): void {
        app.directive("demo", {
          beforeMount(el: any, binding: any) {
            console.log(66666666, binding.value);
          },
          // 指令绑定元素挂载后
          mounted(el: any, binding: any) {
             el.style.backgroundColor = binding.value.color;
          },
        });
      }
    }
    
    

    Vue3.0自定义指令的钩子函数

          // 指令绑定元素挂载前
          beforeMount(el: any) {
            console.log("1");
          },
          // 指令绑定元素挂载后
          mounted(el: any, binding: any) {
            console.log("2");
          },
          // 指令绑定元素因为数据修改触发修改前
          beforeUpdate(el: any) {
            console.log("3");
          },
          // 指令绑定元素因为数据修改触发修改后
          updated(el: any) {
            console.log("4");
          },
          // 指令绑定元素销毁前
          beforeUnmount(el: any) {
            console.log("5");
          },
          // 指令绑定元素销毁后
          unmounted(el: any) {
            console.log("6");
          },
    

    注:

    1. 除 update 与 componentUpdated 钩子函数之外,每个钩子函数都含有 el、binding、vnode 这三个参数,这点不管是Vue2还是3都是相同的
    2. 在每个函数中,第一个参数永远是 el, 表示被绑定了指令的那个 dom 元素,这个el 参数,是一个原生的 JS 对象,所以 Vue 自定义指令可以用来直接和 DOM 打交道
    3. binding 是一个对象,它包含以下属性:name、value、oldValue、expression、arg、modifiers
    4. oldVnode 只有在 update 与 componentUpdated 钩子中生效
    5. 除了 el 之外,binding、vnode 属性都是只读的。
    Vue3局部指令定义和全局指令
    1.新建directive文件夹,文件夹下新建文件。

    如下所示index.ts,

    demo指令

    export default class A {
      constructor() {}
      demo(app: any): void {
        app.directive("demo", {
          beforeMount(el: any) {
            console.log("1");
          },
          // 指令绑定元素挂载后
          mounted(el: any, binding: any) {
            console.log("2");
          },
          // 指令绑定元素因为数据修改触发修改前
          beforeUpdate(el: any) {
            console.log("3");
          },
          // 指令绑定元素因为数据修改触发修改后
          updated(el: any) {
            console.log("4");
          },
          // 指令绑定元素销毁前
          beforeUnmount(el: any) {
            console.log("5");
          },
          // 指令绑定元素销毁后
          unmounted(el: any) {
            console.log("6");
          },
        });
      }
    }
    
    
    2.main.ts(.js)全局挂载引入即可使用
    import A from './directive/index';
    const app = createApp(App);
    const a = new A();
    a.demo(app);
    

    注:Vue3.0使用的createApp的方式进行全局挂载。

    展开全文
  • 背景 在我司的项目中,权限管理方面涉及到...刚好最近有空,就来探究一下组件在Vue3中能否正常运行,如果不能,要怎么做调整。 阅读前提 一定的Vue插件开发知识 对Vue工作原理有所了解 准备工作 1.首先更新一下vue

    背景

    在我司的项目中,权限管理方面涉及到了Vue自定义指令功能,也使用了一些以自定义指令形式调用的插件。Vue3.0发布后,不再直接暴露一个Vue类而且directive()方法注册自定义指令的hooks也有所改变,意味着2.x版本的Vue.use()方法不再可行,同时在注册组件时各个方法挂载在hooks上的位置也要做调整。

    刚好最近有空,就来探究一下组件在Vue3中能否正常运行,如果不能,要怎么做调整。


    阅读前提

    • 一定的Vue插件开发知识
    • 对Vue工作原理有所了解

    准备工作

    1.首先更新一下vue-cli版本

    在这里插入图片描述
    2.create两个Vue项目,一个2.x版本(hello-vue2),一个3.0版本(hello-vue3)做对照试验

    我这里一个是3.0.0-0,一个是2.6.11

    在这里插入图片描述
    在这里插入图片描述


    3.以一款图片查看器插件作为材料进行测试(截止文章发出日期,v-viewer最新版本为1.5.1,还未对Vue3做任何兼容处理),讲道理,这款插件确实好用

    开始

    首先,在hello-vue2中按照v-viewer文档给出的两种方式引用插件,

    <template>
      <div id="app">
        <!-- directive方式引用 -->
        <div class="images" v-viewer>
          <img src="https://picsum.photos/200/200" />
          <img src="https://picsum.photos/300/200" />
          <img src="https://picsum.photos/250/200" />
        </div>
        <!-- component方式引用 -->
        <viewer :images="images">
          <img v-for="src in images" :src="src" :key="src" />
        </viewer>
      </div>
    </template>
    
    <script>
    import "viewerjs/dist/viewer.css";
    import Viewer from "v-viewer";
    import Vue from "vue";
    Vue.use(Viewer)
    export default {
      name: "App",
      data() {
        return {
          images: [
            "https://picsum.photos/200/200",
            "https://picsum.photos/300/200",
            "https://picsum.photos/250/200",
          ],
        };
      },
    };
    </script>
    <style scoped>
    .image {
      height: 200px;
      cursor: pointer;
      margin: 5px;
      display: inline-block;
    }
    </style>
    
    

    不出所料,两种方式都正常运行,效果如图

    在这里插入图片描述
    这里之所以没有在components中注册viewer也可以正常使用,是因为插件的install方法中已经全局注册过了

    //v-viewer/src/index.js
    import {extend} from './utils'
    import Component from './component.vue'
    import directive from './directive'
    import Viewer from 'viewerjs'
    
    export default {
      install (Vue, {name = 'viewer', debug = false, defaultOptions} = {}) {
        Viewer.setDefaults(defaultOptions)
    
        Vue.component(name, extend(Component, { name }))
        Vue.use(directive, {name, debug})
      },
      setDefaults (defaultOptions) {
        Viewer.setDefaults(defaultOptions)
      }
    }
    
    


    接着,在hello-vue3项目中同样使用两种方式引用,同样的代码,就不再贴一遍了。
    果不其然,组件引用的方式正常执行,而自定义指令引用的方式:Uncaught TypeError: Cannot read property 'use' of undefined
    Vue类是undefined,没有use方法
    翻看一下Vue源码,之前的return Vue

    //vue 2.6.11
    (function (global, factory) {
      typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
      typeof define === 'function' && define.amd ? define(factory) :
      (global = global || self, global.Vue = factory());
    }(this, function () { 'use strict';
      ...
      ...
      return Vue;
    
    }));
    

    变成了

    //vue 3.0.0-0
    var Vue = (function (exports) {
      'use strict';
      ...
      ...
      exports.BaseTransition = BaseTransition;
      exports.Comment = Comment;
      exports.Fragment = Fragment;
      ...
      ...
      exports.withKeys = withKeys;
      exports.withModifiers = withModifiers;
      exports.withScopeId = withScopeId;
    
      return exports;
    
    }({}));
    

    按照作者尤大在发布会上表露的思想和文档中的描述,此改动旨在促进开发者们不再通过选项来组织代码,而应该将代码通过自定义选项来组织成为处理特定功能的函数。

    The APIs proposed in this RFC provide the users with more flexibility when organizing component code. Instead of being forced to always organize code by options, code can now be organized as functions each dealing with a specific feature. The APIs also make it more straightforward to extract and reuse logic between components, or even outside components.

    所以Vue不再支持直接引入一个Vue类,而是将具体api暴露出来供开发者选择使用。2.x版本中const app = new Vue(options)来创建一个实例的方法也变成了:

    import { createApp } from 'vue'
    import { App } from './App'
    const app = createApp(App)
    

    再回到源码中,use的使用者找到了,那么,使用者是否拥有use方法呢?

    //vue/dist/vue.global.js
    function createAppAPI(render, hydrate) {
      ...
      ...
      use(plugin, ...options) {
          if (installedPlugins.has(plugin)) {
               warn(`Plugin has already been applied to target app.`);
          }
          else if (plugin && isFunction(plugin.install)) {
              installedPlugins.add(plugin);
              plugin.install(app, ...options);
          }Ï
          else if (isFunction(plugin)) {
              installedPlugins.add(plugin);
              plugin(app, ...options);
          }
          else {
              warn(`A plugin must either be a function or an object with an "install" ` +
                  `function.`);
          }
          return app;
      },
      ...
      ...
    }
    

    还好,use方法仍然保留,那么我们将调用方法修改一下,放在main.js中,同时将组件引用的方式去掉

    //main.js
    import { createApp } from 'vue'
    import App from './App.vue'
    import Viewer from 'v-viewer'
    import "viewerjs/dist/viewer.css";
    const app = createApp(App)
    app.use(Viewer)
    app.mount('#app')
    
    

    ok,报错没有了,但是插件并没有生效,点击无反应

    在这里插入图片描述
    结合注册directive时的生命周期有所改变这一点,猜测可能是注册方法静默失败了。找到插件的安装方法,加上log调试一下:

    //v-viewer/dist/v-viewer.js
    var install = function install(Vue, _ref) {
      ...
      ...
      console.log('我要进入绑定方法了!')
      Vue.directive('viewer', {
        bind: function bind(el, binding, vnode) {
          console.log('我开始绑定了!')
          log('viewer bind');
          var debouncedCreateViewer = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1_throttle_debounce__["a" /* debounce */])(50, createViewer);
          debouncedCreateViewer(el, binding.value);
    
          createWatcher(el, binding, vnode, debouncedCreateViewer);
    
          if (!binding.modifiers.static) {
            createObserver(el, binding.value, debouncedCreateViewer, binding.modifiers.rebuild);
          }
        },
        unbind: function unbind(el, binding) {
          log('viewer unbind');
    
          destroyObserver(el);
    
          destroyWatcher(el);
    
          destroyViewer(el);
        }
      });
    }
    

    在这里插入图片描述
    果然,注册插件的核心方法没有执行,那么我们应该对照文档,将hooks改为对应正确的。先看一下2.x中对钩子的定义:

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

    还有3.0的

    //vue3
    app.directive('my-directive', {
      // Directive has a set of lifecycle hooks:
      // called before bound element's parent component is mounted
      beforeMount() {},
      // called when bound element's parent component is mounted
      mounted() {},
      // called before the containing component's VNode is updated
      beforeUpdate() {},
      // called after the containing component's VNode and the VNodes of its children // have updated
      updated() {},
      // called before the bound element's parent component is unmounted
      beforeUnmount() {},
      // called when the bound element's parent component is unmounted
      unmounted() {}
    })
    

    由于v-viewer只用到了bind()unbind()两个钩子,且钩子中并没有涉及到挂载dom的逻辑,所以这里bind可以选择替换为beforeMount或者mountedunbind可以替换为beforeUnmount或者unmounted

    代码修改如下:

    // 省略无关部分
    Vue.directive('viewer', {
        beforeMount: function bind(el, binding, vnode) {
          console.log('我开始绑定了!')
          log('viewer bind');
          var debouncedCreateViewer = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1_throttle_debounce__["a" /* debounce */])(50, createViewer);
          debouncedCreateViewer(el, binding.value);
    
          createWatcher(el, binding, vnode, debouncedCreateViewer);
    
          if (!binding.modifiers.static) {
            createObserver(el, binding.value, debouncedCreateViewer, binding.modifiers.rebuild);
          }
        },
        unmounted: function unbind(el, binding) {
          log('viewer unbind');
    
          destroyObserver(el);
    
          destroyWatcher(el);
    
          destroyViewer(el);
        }
      });
    

    结果如图:

    在这里插入图片描述
    方法进来了,也带来的新的报错:Vue.nextTick is not a function。这想必与开始的Uncaught TypeError: Cannot read property 'use' of undefined是一个原因导致的。找到报错的地方:

    //v-viewer/dist/v-viewer.js
    var install = function install(Vue, _ref) {
       ...
       ...
       Vue.nextTick(function () {
         if (rebuild || !el['$' + name]) {
           destroyViewer(el);
           el['$' + name] = new __WEBPACK_IMPORTED_MODULE_0_viewerjs___default.a(el, options);
           log('viewer created');
         } else {
           el['$' + name].update();
           log('viewer updated');
         }
       });
     }
      ...
      ...
    }
    

    虽然这个地方的Vue与之前的Vue是一样的(都是一个vue实例),但是3.0版本中nextTick方法不再挂载在原型上,而是作为一个api单独暴露。此处如果选择导入nextTick方法会略微有些麻烦,我们借鉴Vue 2.x版本中,对于低版本不支持promise()的浏览器用setTimeout()代替的做法。

    //v-viewer/dist/v-viewer.js
    var install = function install(Vue, _ref) {
      ...
      ...
      function createViewer(el, options) {
        var rebuild = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
    
        setTimeout(() => {
          if (rebuild || !el['$' + name]) {
            destroyViewer(el);
            el['$' + name] = new __WEBPACK_IMPORTED_MODULE_0_viewerjs___default.a(el, options);
            log('viewer created');
          } else {
            el['$' + name].update();
            log('viewer updated');
          }
        }, 0);
      }
      ...
      ...
    }
    

    在这里插入图片描述
    done,it’s worked!


    心得

    • Vue 2.x相较于3.0版本,注册自定义指令并没有什么大的变动。调用方式方面,除了文中的调用方式,文档中还有另外两种:
    // 第二个参数使用函数,默认在mounted和updated钩子中执行
    app.directive('my-directive', () => {
      // do something
    })
    
    // 省略第二个参数:getter,返回指令的定义内容
    const myDirective = app.directive('my-directive')
    
    • v-viewer插件之所以只做了少量修改即可兼容vue3(不一定是完美兼容),原因在于插件在注册时以及插件本身的逻辑极少依赖于Vue的底层逻辑
    • 虽然Vue3对老版本代码做了兼容,但是仍有必要使用Composition Api风格对老代码进行重构,使逻辑更为清晰
    • 在阅读Vue文档过程中,越来越觉得Composition Api的写法像极了React

    如有错误,欢迎指正!

    展开全文
  • 业务需求除了路由需要控制外点击按钮也需要做到控制,百度了vue2相关资料发现大部分都是使用自定义指令实现控制按钮显示和隐藏,刚好手头上做了个demo 写个文档记录下; vue2和vue3directive的函数也不一样 bind → ...

    业务需求除了路由需要控制外点击按钮也需要做到控制,百度了vue2相关资料发现大部分都是使用自定义指令实现控制按钮显示和隐藏,刚好手头上做了个demo 写个文档记录下;
    vue2和vue3directive的函数也不一样

    bind → beforeMount
    inserted → mounted
    beforeUpdate: new! this is called before the element itself is updated, much like the component lifecycle hooks.
    update → removed! There were too many similarities to updated, so this is redundant. Please use updated instead.
    componentUpdated → updated
    beforeUnmount: new! similar to component lifecycle hooks, this will be called right before an element is unmounted.
    unbind -> unmounted
    

    前置条件

    首先路由的meta中需要包含按钮的权限信息方便获取;例如:
    在这里插入图片描述
    新建一个vuex模块或者新建一个reactive响应式文件存储值
    在这里插入图片描述
    在Layout里存储权限值(Layout是所有路由视图的公共组件)

    ...
    import { useStore } from "vuex";
    import { useRoute } from 'vue-router'
    ...
    export default {
    	setup() {
    		const store = useStore();
    		const $route = useRoute();
    		
    		watchEffect(() => {
    			const { add, edit, detail, del, more } = $route.meta;
    			const role = { add, edit, detail, del, more };
    			for(const item in role) {
    	        	store.commit('role/setRole', { name: item, type :role[item] })
    	      	}
    		}
    	}
    }
    

    注册全局指令
    在main.js中输入

    ...
    import store from './store'
    const app =  createApp(App);
    
    app.directive('permission', {
    	mounted(el, binding) {
    		if (!store.state.role[binding.value]) {
    			el.parentNode && el.parentNode.removeChild(el)
    		}
    	}
    }
    ...
    

    在需要用到控制权限的button按钮上输入

    <a-button v-permission="'add'" type="primary" @click="onSubmit">确 定</a-button>
    

    就可以轻松实现按钮的控制了

    参考资料:
    vue 官方文档
    vue3 directive挂载生命周期
    directive el.parentNode为null的相关处理

    展开全文
  • 除了核心功能默认的内置指令(例如v-model,v-if,v-show),vue也允许注册自定义指令。初次使用vue3,相对于vue2的小差异难免有些小不适应。针对于项目中需要的按钮防连击自定义了一个指定。记录一下~ 首先我们需要...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 666
精华内容 266
关键字:

vue3自定义指令

vue 订阅