资讯专栏INFORMATION COLUMN

彻底搞懂elementUI指令与服务模式原理

freewolf / 1197人阅读

摘要:不甘做轮子的搬运工记录一个实习菜鸟写图片预览组件的艰辛道路很多组件中使用了指令模式和服务模式,比如以下以组件为例指令模式全屏覆盖服务模式跟大多数萌新一样,啥是服务先看看的目录结构打开目录,找到其下目录文件中有一大坨组件注册信息重点找

不甘做轮子的搬运工!!!

记录一个实习菜鸟写图片预览组件的艰辛道路~

elementUI很多组件中使用了指令模式和服务模式,比如:loadingmessage...
以下以loading组件为例: 指令模式:
服务模式:
const loading = this.$loading({
  lock: true,
  text: "Loading",
  spinner: "el-icon-loading",
  background: "rgba(0, 0, 0, 0.7)"
});
跟大多数萌新一样,啥是服务?!

先看看elmentUI的目录结构:

打开node_modules目录,找到其下elementUI目录:

element-uisrcindex.js文件中有一大坨组件注册信息,重点找到我们要找的loading...

// ...
// directive 指令装载
Vue.use(Loading.directive)
// prototype 服务装载
Vue.prototype.$loading = Loading.service
// ...

Vue.use() 这个指令是 Vue 用来安装插件的,如果传入的参数是一个对象,则该对象要提供一个 install 方法,如果是一个函数,则该函数被视为 install 方法,在 install 方法调用时,会将 Vue 作为参数传入。

开始叭!

先看看loading/index.js文件中是什么鬼?

//引入指令文件和服务文件,directive为指令模式文件,index.js为服务模式文件
import directive from "./src/directive";
import service from "./src/index";

export default {
  //install方法注册组件,不在赘述install的用法,star-pic-list图片预览组件文章中已经介绍过
  install(Vue) {
    Vue.use(directive);
    //在vue的原型对象上注册一个$loading的对象,这个$loading非常眼熟,看上面服务模式的使用,用到了this.$loading,源头找到了
    Vue.prototype.$loading = service;
  },
  //引入的directive文件
  directive,
  //引入的index.js文件
  service
};
v-loading 指令解析

篇幅太长,其中我们只取 fullscreen 修饰词。

// 引入 .vue 文件
import Vue from "vue"
// 引入loading.vue基础文件,里面包含的是组件的基础结构,如html结构,loading显示的页面结构都在这里面
import Loading from "./loading.vue"
// 后面重点讲解extend()构造器
// Vue.extend() 是vue构造器,它返回的是一个扩展实例构造器,也就是预设了部分选项的Vue实例构造器,

// mask字面意思是面具,掩饰,可以猜出来,这个通过Vue.extend(Loading)返回构造器应该是用于我们loading加载时的遮罩层用的
// loading就是预设选项,就像vue示例中,有components,name,data,methods...好像有点明白了
const Mask = Vue.extend(Loading)

const loadingDirective = {}
// 还记得 Vue.use() 的使用方法么?若传入的是对象,该对象需要一个 install 属性
loadingDirective.install = Vue => {
  // toggleLoading 方法看名字就是切换loading显示和隐藏的嘛~
  const toggleLoading = (el, binding) => {
    // 若绑定值为 truthy 则插入 loading 元素
    // binding 值是一个对象,有指令名、指令的绑定值、modifiers修饰符对象等等等等,具体的可以去了解自定义指令相关内容
    if (binding.value) {  //binding.value是绑定的指令值
      if (binding.modifiers.fullscreen) {  还记得我们插入的指令吗?:v-loading.fullscreen="true" ,  .fullscreen就是修饰符
        insertDom(document.body, el, binding)    //insertDom看名字就知道是插入新的元素
      }
     // visible 是loading.vue   data里面定义的值
    } else {
      el.instance.visible = false
    }
  }

  const insertDom = (parent, el, binding) => {
    // loading 设为可见
    el.instance.visible = true
    // appendChild 添加的元素若为同一个,则不会重复添加
    parent.appendChild(el.mask)
  }
  // 在此注册 directive 指令
  Vue.directive("loading", {
    bind: function(el, binding, vnode) {
      // 创建一个子组件,这里和 new Vue(options) 类似
      // 返回一个组件实例
      const mask = new Mask({
        el: document.createElement("div"),
        // 有些人看到这里会迷惑,为什么这个 data 不按照 Vue 官方建议传函数进去呢?
        // 其实这里两者皆可
        // 稍微做一点延展好了,在 Vue 源码里面,data 是延迟求值的
        // 贴一点 Vue 源码上来
        // return function mergedInstanceDataFn() {
        //   let instanceData = typeof childVal === "function"
        //     ? childVal.call(vm, vm)
        //     : childVal;
        //   let defaultData = typeof parentVal === "function"
        //     ? parentVal.call(vm, vm)
        //     : parentVal;
        //   if (instanceData) {
        //     return mergeData(instanceData, defaultData)
        //   } else {
        //     return defaultData
        //   }
        // }
        // instanceData 就是我们现在传入的 data: {}
        // defaultData 就是我们 loading.vue 里面的 data() {}
        // 看了这段代码应该就不难理解为什么可以传对象进去了
        data: {
          fullscreen: !!binding.modifiers.fullscreen
        }
      })
      // 将创建的子类挂载到 el 上
      // 在 directive 的文档中建议
      // 应该保证除了 el 之外其他参数(binding、vnode)都是只读的
      el.instance = mask
      // 挂载 dom
      // bind 只会调用一次,在bind 的时候给 el.mask 赋值,因此el.mask 所指的为同一个 dom 元素
      el.mask = mask.$el
      // 若 binding 的值为 truthy 运行 toogleLoading
      binding.value && toggleLoading(el, binding)
    },
    update: function(el, binding) {
      // 若旧不等于新值得时候(一般都是由 true 切换为 false 的时候)
      if (binding.oldValue !== binding.value) {
        // 切换显示或消失
        toggleLoading(el, binding)
      }
    },
    unbind: function(el, binding) {
      // 当组件 unbind 的时候,执行组件销毁
      el.instance && el.instance.$destroy()
    }
  })
}
export default loadingDirective
关于extend()更多内容请参考这里,非常通熟易懂!
loading服务方式调用原理

直接看源码:

import Vue from "vue"
import loadingVue from "./loading.vue"
// 和指令模式一样,创建实例构造器
const LoadingConstructor = Vue.extend(loadingVue)
// 定义变量,若使用的是全屏 loading 那就要保证全局的 loading 只有一个
let fullscreenLoading
// 这里可以看到和指令模式不同的地方
// 在调用了 close 之后就会移除该元素并销毁组件
LoadingConstructor.prototype.close = function() {
  setTimeout(() => {
    if (this.$el && this.$el.parentNode) {
      this.$el.parentNode.removeChild(this.$el)
    }
    this.$destroy()
  }, 3000)
}

const Loading = (options = {}) => {
  // 若调用 loading 的时候传入了 fullscreen 并且 fullscreenLoading 不为 falsy
  // fullscreenLoading 只会在下面赋值,并且指向了 loading 实例
  if (options.fullscreen && fullscreenLoading) {
    return fullscreenLoading
  }
  // 这里就不用说了吧,和指令中是一样的
  let instance = new LoadingConstructor({
    el: document.createElement("div"),
    data: options
  })
  let parent = document.body
  // 直接添加元素
  parent.appendChild(instance.$el)
  // 将其设置为可见
  // 另外,写到这里的时候我查阅了相关的资料
  // 自己以前一直理解 nextTick 是在 dom 元素更新完毕之后再执行回调
  // 但是发现可能并不是这么回事,后续我会继续研究
  // 如果干货足够的话我会写一篇关于 nextTick ui-render microtask macrotask 的文章
  Vue.nextTick(() => {
    instance.visible = true
  })
  // 若传入了 fullscreen 参数,则将实例存储
  if (options.fullscreen) {
    fullscreenLoading = instance
  }
  // 返回实例,方便之后能够调用原型上的 close() 方法
  return instance
}
export default Loading
现学现用-写一个点击图片预览的组件试试看 目录结构:

directive.js是指令模式文件,index.js是服务模式文件,star-pic-preview.vue是基础单文件,包含了基础的html结构

先看指令模式:

使用:

我直接使用了指令,并没有传参,因为功能简单,默认参数就是false

 

效果:

点击出现遮罩层,图片居中显示预览

服务模式:

使用:

 methods: {
    // 服务方式
    openImagePreview2(e) {
        // 如果只传图片
        this.$picPreview(e.target.src);
        //如果传复杂对象,可以配置遮罩层的背景颜色等...
        // this.$picPreview({
        //     background: "rgba(0, 0, 0, 0.7)",
        //     imageUrl: e.target.src,
        // });
    },
}

效果如上

源码请参考github地址: 源码地址

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109223.html

相关文章

  • 线程安全(上)--彻底搞懂volatile关键字

    摘要:此时,就出现了线程不安全问题了。因为的初始值会是因此,重排序是有可能导致线程安全问题的。真的能完全保证一个变量的线程安全吗我们通过上面的讲解,发现关键字还是挺有用的,不但能够保证变量的可见性,还能保证代码的有序性。 对于volatile这个关键字,相信很多朋友都听说过,甚至使用过,这个关键字虽然字面上理解起来比较简单,但是要用好起来却不是一件容易的事。 这篇文章将从多个方面来讲解vol...

    teren 评论0 收藏0
  • 彻底搞懂JavaScript执行机制

    摘要:彻底搞懂执行机制首先我们大家都了解的是,是一门单线程语言,所以我们就可以得出是按照语句顺序执行的首先看这个显然大家都知道结果,依次输出,然而换一种这个时候再看代码的顺序执行,输出,,,。不过即使主线程为空,也是达不到的,根据标准,最低是。 彻底搞懂JavaScript执行机制 首先我们大家都了解的是,JavaScript 是一门单线程语言,所以我们就可以得出: JavaScript 是...

    hizengzeng 评论0 收藏0
  • 一篇文章带你彻底搞懂NIO

    摘要:阻塞当进行读写时,线程是阻塞的状态。当任何一个收到数据后,中断程序将唤起进程。接收数据当收到数据后,中断程序会给的就绪列表添加引用。当接收到数据,中断程序一方面修改,另一方面唤醒等待队列中的进程,进程再次进入运行状态如下图。 本篇文章目的在于基本概念和原理的解释,不会贴过多的使用代码。 什么是NIO Java NIO (New IO)是 Java 的另一个 IO API (来自 jav...

    ziwenxie 评论0 收藏0
  • 来一轮带注释的demo,彻底搞懂javascript中的replace函数

    摘要:对应于上述的,等。匹配到的子字符串在原字符串中的偏移量。插入当前匹配的子串右边的内容。 javascript这门语言一直就像一位带着面纱的美女,总是看不清,摸不透,一直专注服务器端,也从来没有特别重视过,直到最近几年,javascript越来越重要,越来越通用。最近和前端走的比较近,借此机会,好好巩固一下相关知识点。 1.初识replace 在js中有两个replace函数 一个是lo...

    Coding01 评论0 收藏0
  • 彻底搞懂JavaScript中的继承

    摘要:这正是我们想要的太棒了毫不意外的,这种继承的方式被称为构造函数继承,在中是一种关键的实现的继承方法,相信你已经很好的掌握了。 你应该知道,JavaScript是一门基于原型链的语言,而我们今天的主题 -- 继承就和原型链这一概念息息相关。甚至可以说,所谓的原型链就是一条继承链。有些困惑了吗?接着看下去吧。 一、构造函数,原型属性与实例对象 要搞清楚如何在JavaScript中实现继承,...

    _ivan 评论0 收藏0

发表评论

0条评论

freewolf

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<