手把手教你用指令(directives)写个仿 element-ui 的v-loading

说来惭愧,用 Vue 这么长时间了,今天第一次用指令。

是出于什么契机呢?

主要是今天我们需要优化一下 loading 的效果,之前项目中用的都是 element-ui 的v-loading,现在我们网站的审美提上来了,过去的效果已经跟不上我们“罗网”的气质了。修改默认 loading 效果的方法千千万,我为啥要自定义指令来写一个 loading 呢,因为我没用过,我就想用一下,我愿意,而且以后再做修改的话,这个方案的可扩展性更好,想怎么改就怎改,不用抠抠搜搜地去改样式。

提示:本文只针对有 Vue 开发经验的同学。看完这篇文章,大家应该能够写一些东西了,如果看完还不会,很正常,每个人都有自己的天赋,对于不是自己天赋方面的东西多看几遍就好了,大家都是智商正常的人类,没有什么东西是他能学会,你不能学会的,所以不用着急,跟着节奏慢慢来就可以。分享一个我奶奶留下来的家训“活到老,学到老,还有一招学不到”,所以大家要对自己有信心。

今天排插炸了,还好没炸的很大,只是小小的炸了一下起了点火星子冒了点小烟,没炸到脸。我可是没开空调、没坐在电热毯里,穿个睡衣坐在书桌前写的文啊。还好有我们王刚王老师推荐的“每日坚果”补充能量,加班写文必备,代码的好伴侣,大家快去买。

本文大纲

  • 什么是指令?
  • 常见的默认指令
  • 什么是自定义指令?我会带着大家过一下 Vue 官网文档上的demo
  • 结合具体的业务场景写一个自定义的loading指定 ,暂定 v-cloading
  • 总结 指令比较适合哪些应用场景
  • 参考文档

什么是指令?

  • 指令是带有 v-前缀的特殊属性
  • 当表达式的值改变时,将其产生的 连带影响,响应式地作用于 DOM

第一句换很好理解,第二句我们在接下来的demo中会让你直观的感受到这句话的意思。

常见的默认指令

这里我们就列举三个常见的指令,想看更多指令可以看看 Vue 官方文档,或是 Vue指令基本使用大全这篇博文,这篇文章列举出了挺多的。

v-model

  • 作用:在表单元素上创建双向的数据绑定

  • 说明:监听用户的输入事件以更新数据


Message is: {{ message }}

v-on

  • 作用:绑定事件
  • 语法:v-on:click="say" or v-on:click="say('参数', $event)"
  • 简写:@click="say"
  • 说明:绑定的事件从methods中获取







 

v-bind

  • 作用:当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
  • 语法:v-bind:title="msg"
  • 简写::title="msg"





什么是自定义指令

这里我会把 官方文档上的 demo 和一些必要的说明搬过来。为什么要这么做?为了从来没接触过自定义指令的同学不用自己查找切换看文档。只需要看完这一篇,了解一些基本的概念,就可以循序渐进的带你写一个 v-cloading 自定义loading的指令。如果有想了解源码的同学,可以暂时移步,网上有很多介绍 v-loading 带你解读源码的文章。这里我们是第一次使用,所以只会浅显地介绍应用。

Vue 官方文档上都是以全局注册为例子,在这里我们就都以局部注册为例子。

demo1:当页面加载时,input 自动获取焦点

我们先上代码




在这里我们不用过多纠结每一行代码是什么,比如 inserted 是什么,inserted 是指令的一个钩子函数,不用着急,后面我们会具体介绍一下 指令的钩子函数的。这个demo只是让我们先感受下指令是什么,能做什么,是不是很简单,感觉自己随随便也能写一个 demo 了。

结果:可以看到确实是页面一加载就 input 就获取到了焦点

image-20191221110017591.png

demo2:带你感受指令的钩子函数

虽然代码应该是很客观很理性的东西,但是学习的过程中,感受也很重要,感受会影响你想不想学,如果只是冰冷的定义我觉得会很难理解一个东西。你感受到了,也自然就学会了。

我先列一下官网对于钩子函数的定义和描述,然后我会结合具体的例子,让大家感受一下钩子函数是怎样发挥作用的,触发的时机是什么。

一个指令定义对象可以提供如下几个钩子函数(均为可选):

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

关于钩子函数其实我理解的也不是和深刻,就以 demo 去理解,如果有同学更加理解钩子函数,可以告诉我,大家一起交流一下。这个 demo 也是看别人写的。



当我们刷新页面时,指令显示被绑定到了 dom 上,然后被插入到了父节点中。


image-20191221135933395.png

当我们点击 “更换title” 按钮时,其实指定绑定的元素肯定是会更新的,但是指令的 value 值是还没有更新的,仍然是 red。

image-20191221140302702.png

当我们点击 “更换 color” 按钮时,指令的值就发生变化而,由 red 变成了 blue。

image-20191221140510817.png

当我们点击 “测试解绑 v-color”,我们其实就是销毁了指令所绑定的组件,指令就解绑了。

image-20191221140652888.png

demo3:钩子函数参数

在上述 demo 中我们看到钩子函数的参数有 el、binding 等,可能不是很理解,这一个 demo 就带大家了解一下钩子函数的参数。

照例我们先看看官网是怎么说明钩子函数参数的。

  • el:指令所绑定的元素,可以用来直接操作 DOM

  • binding:一个对象,包含一下属性:

    • name:指令名,不包括v-前缀

    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。

    • oldValue :指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。

    • expression :字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"

    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"

    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }

  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。

  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

Warning:

除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。



我们来看下结果:是不是很简单参数就那些


image-20191221171512129.png

文档中关于动态指令参数、字面量我就不接说了,大家自己看下文档就可以了。

带大家实现一个仿 element-UI 的 v-loading

上面的基本知识介绍了那么多,我们终于可以综合运用来写一个实用的例子了。

首先我们看下 element 的 v-loading 有哪些属性


image-20191221172040629.png

但是这里我们不会实现上面那么多属性,因为我懒,demo并非本人原创,我只是做了修改。

我们今天的 demo 实现:fullscreen text spinner background 等属性,我们的自定义指令 我们就叫它 v-cloading 吧。需要loading的时候我们就创建一个实例,把它挂到父级元素上去。

首先我们先准备一个 loading 的模板 mask.vue:

这个没什么好讲的,就是定义了一下 loading 的效果长什么样





接下来我们看下最关键的部分,指令的实现部分,其实这部分的代码和 element v-loading 本身的实现比较相似。虽然这块代码还不算特别完善,但是基本的实现是可以的,这块我会重点讲一下。等我认真阅读完 element-UI v-loading 的源码后,会再进行完善的。

import Vue from 'vue'
import maskLoading from './mask.vue'

// 我们通过模板 构造一个 Mask
const Mask = Vue.extend(maskLoading)
// Mask 是否需要更新,也就是 loading 展示效果是否需要更新
const toggleLoading = (el, binding) => {
  // 如果指令传入的值为 true 或是有值,就显示这个模板,挂到父级元素上去或是body上
  if (binding.value) {
    Vue.nextTick(() => {
      if (binding.modifiers.fullscreen) {
        // 全屏的话就挂载到 body 上
        document.body.appendChild(el.mask)
      } else {
        // 非全屏就挂到当前组件上去
        let height = el.clientHeight
        let width = el.clientWidth
        let offsetTop = el.offsetTop
        el.mask.style.top = offsetTop + 'px'
        el.mask.style.height = height + 'px'
        el.mask.style.width = width + 'px'
        el.appendChild(el.mask)
      }
    })
  } else {
    // 如果传入的值是 false,或是没有值,就销毁 Mask
    el.mask && el.mask.parentNode && el.mask.parentNode.removeChild(el.mask)
    el.instance && el.instance.$destroy()
  }
}

Vue.directive('cloading', {
  bind (el, binding) {
    // 指令第一次绑定到元素上时,初始化一些属性,这些属性可以通过字面量的形式传,也可以通过 dataset或是其他方式,我还没想好。
    let background = binding.value.background
    let text = binding.value.text
    let iconSrc = binding.value.iconSrc
    let iconWidth = binding.value.iconWidth
    let iconHeight = binding.value.iconHeight
    let color = binding.value.color
    let fontSize = binding.value.fontSize
    console.log('binding.value: ', binding.value)
    // 构造了一个 Mask 实例
    const mask = new Mask({
      el: document.createElement('div'),
      data: {
        fullscreen: !!binding.modifiers.fullscreen,
        background: background || '255, 255, 255, 0.9',
        text: text || '加载中...',
        iconSrc: iconSrc || require('../../assets/images/icn_loading.png'),
        iconWidth: iconWidth || null,
        iconHeight: iconHeight || null,
        color: color || null,
        fontSize: fontSize || null
      }
    })
    el.instance = mask
    el.mask = mask.$el
    // 更新 Mask的展示
    toggleLoading(el, binding)
  },
  // 所在组件的 VNode 更新时调用
  update (el, binding) {
    if (binding.oldValue !== binding.value) {
      toggleLoading(el, binding)
    }
  },
  unbind (el) {
    el.mask && el.mask.parentNode && el.mask.parentNode.removeChild(el.mask)
    el.instance && el.instance.$destroy()
  }
})

这样写下来,发现也没什么可讲的,一切都很自然。

铛铛~,我们来看下效果:

q8mnk-doexw.gif

还蛮好看的。是不是很简单。

参考文档

  • Vue指令基本使用大全

  • vue官方文档·自定义指令

  • https://juejin.im/post/5d4f50e0e51d4561ee1bdf43

  • https://blog.csdn.net/qq_32858649/article/details/94433418

你可能感兴趣的:(手把手教你用指令(directives)写个仿 element-ui 的v-loading)