Vue组件的三种调用方式

最近在写fj-service-system的时候,遇到了一些问题。那就是我有些组件,比如Dialog、Message这样的组件,是引入三方组件库,比如element-ui这样的,还是自己实现一个?虽然它们有按需引入的功能,但是整体风格和我的整个系统不搭。于是就可以考虑自己手动实现这些简单的组件了。

通常我们看Vue的一些文章的时候,我们能看到的通常是讲Vue单文件组件化开发页面的。单一组件开发的文章相对就较少了。我在做fj-service-system项目的时候,发现其实单一组件开发也是很有意思的。可以写写记录下来。因为写的不是什么ui框架,所以也只是一个记录,没有github仓库,权且看代码吧。

  • v-model或者.sync显式控制组件显示隐藏
  • 通过js代码调用
  • 通过Vue指令调用

在写组件的时候很多写法、灵感来自于element-ui,感谢。

Dialog

我习惯把这个东西叫做对话框,实际上还有叫做modal(弹窗)组件的叫法。其实就是在页面里,弹出一个小窗口,这个小窗口里的内容可以定制。通常可以用来做登录功能的对话框。

这种组件就很适合通过v-model或者.sync的方式来显式的控制出现和消失。它可以直接写在页面里,然后通过data去控制——这也是最符合Vue的设计思路的组件。

为此我们可以写一个组件就叫做Dialog.vue

<template>
  <div class="dialog">
    <div class="dialog__wrapper" v-if="visble" @clcik="closeModal">
      <div class="dialog">
        <div class="dialog__header">
          <div class="dialog__title">{{ title }}</div>
        </div>
        <div class="dialog__body">
          <slot></slot>
        </div>
        <div class="dialog__footer">
          <slot name="footer"></slot>
        </div>
      </div>
    </div>
    <div class="modal" v-show="visible"></div>
  </div>
</template>

<script>
  export default {
    name: 'dialog',
    props: {
      title: String,
      visible: {
        type: Boolean,
        default: false
      }
    },
    methods: {
      close() {
        this.$emit('update:visible', false) // 传递关闭事件
      },
      closeModal(e) {
        if (this.visible) {
          document.querySelector('.dialog').contains(e.target) ? '' : this.close(); // 判断点击的落点在不在dialog对话框内,如果在对话框外就调用this.close()方法关闭对话框
        }
      }
    }
  }
</script>

CSS什么的就不写了,跟组件本身关系比较小。不过值得注意的是,上面的dialog__wrapper这个class也是全屏的,透明的,主要用于获取点击事件并锁定点击的位置,通过DOM的Node.contains()方法来判断点击的位置是不是dialog本身,如果是点击到了dialog外面,比如半透明的modal层那么就派发关闭事件,把dialog给关闭掉。

当我们在外部要调用的时候,就可以如下调用:

<template>
  <div class="xxx">
    <dialog :visible.sync="visible"></dialog>
    <button @click="openDialog"></button>
  </div>
</template>

<script>
  import Dialog from 'Dialog'
  export default {
    components: {
      Dialog
    },
    data() {
      return {
        visible: false
      }
    },
    methods: {
      openDialog() {
        this.visible = true // 通过data显式控制dialog
      }
    }
  }
</script>

为了Dialog开启和关闭好看点,你可试着加上<transition></transition>组件配合上过渡效果,简单的一点过渡动效也将会很好看。

Notice

这个组件类似于element-ui的message(消息提示)。它吸引我的最大的地方在于,它不是通过显式的在页面里写好组件的html结构通过v-model去调用的,而是通过在js里通过形如this.$message()这样的方法调用的。这种方法虽然跟Vue的数据驱动的思想有所违背。不过不得不说在某些情况下真的特别方便。

对于Notice这种组件,一次只要提示几个文字,给用户简单的消息提示就行了。提示的信息可能是多变的,甚至可以出现叠加的提示。如果通过第一种方式去调用,事先就得写好html结构,这无疑是麻烦的做法,而且无法预知有多少消息提示框。而通过js的方法调用的话,只需要考虑不同情况调用的文字、类型不同就可以了。

而之前的做法都是写一个Vue文件,然后通过components属性引入页面,显式写入标签调用的。那么如何将组件通过js的方法去调用呢?

这里的关键是Vue的extend方法

文档里并没有详细给出extend能这么用,只是作为需要手动mount的一个Vue的组件构造器说明了一下而已。

通过查看element-ui的源码,才算是理解了如何实现上述的功能。

首先依然是创建一个Notice.vue的文件

<template>
  <div class="notice">
    <div class="content">
      {{ content }}
    </div>
  </div>
</template>

<script>
  export default {
    name: 'notice',
    data () {
      return {
        visible: false,
        content: '',
        duration: 3000
      }
    },
    methods: {
      setTimer() {
        setTimeout(() => {
          this.close() // 3000ms之后调用关闭方法
        }, this.duration)
      },
      close() {
        this.visible = false
        setTimeout(() => {
          this.$destroy(true)
          this.$el.parentNode.removeChild(this.$el) // 从DOM里将这个组件移除
        }, 500)
      }
    },
    mounted() {
      this.setTimer() // 挂载的时候就开始计时,3000ms后消失
    }
  }
</script>

上面写的东西跟普通的一个单文件Vue组件没有什么太大的区别。不过区别就在于,没有props了,那么是如何通过外部来控制这个组件的显隐呢?

所以还需要一个js文件来接管这个组件,并调用extend方法。同目录下可以创建一个index.js的文件。

import Vue from 'vue'

const NoticeConstructor = Vue.extend(require('./Notice.vue')) // 直接将Vue组件作为Vue.extend的参数

let nId = 1

const Notice = (content) => {
  let id = 'notice-' + nId++

  const NoticeInstance = new NoticeConstructor({
    data: {
      content: content
    }
  }) // 实例化一个带有content内容的Notice

  NoticeInstance.id = id
  NoticeInstance.vm = NoticeInstance.$mount() // 挂载但是并未插入dom,是一个完整的Vue实例
  NoticeInstance.vm.visible = true
  NoticeInstance.dom = NoticeInstance.vm.$el
  document.body.appendChild(NoticeInstance.dom) // 将dom插入body
  NoticeInstance.dom.style.zIndex = nId + 1001 // 后插入的Notice组件z-index加一,保证能盖在之前的上面
  return NoticeInstance.vm
}

export default {
  install: Vue => {
    Vue.prototype.$notice = Notice // 将Notice组件暴露出去,并挂载在Vue的prototype上
  }
}

这个文件里我们能看到通过NoticeConstructor我们能够通过js的方式去控制一个组件的各种属性。最后我们把它注册进Vue的prototype上,这样我们就可以在页面内部使用形如this.$notice()方法了,可以方便调用这个组件来写做出简单的通知提示效果了。

当然别忘了这个相当于一个Vue的插件,所以需要去主js里调用一下Vue.use()方法:

// main.js

// ...
import Notice from 'notice/index.js'

Vue.use(Notice)

// ...

Loading

在看element-ui的时候,我也发现了一个很有意思的组件,是Loading,用于给一些需要加载数据等待的组件套上一层加载中的样式的。这个loading的调用方式,最方便的就是通过v-loading这个指令,通过赋值的true/false来控制Loading层的显隐。这样的调用方法当然也是很方便的。而且可以选择整个页面Loading或者某个组件Loading。这样的开发体验自然是很好的。

其实跟Notice的思路差不多,不过因为涉及到directive,所以在逻辑上会相对复杂一点。

平时如果不涉及Vue的directive的开发,可能是不会接触到modifiers、binding等概念。参考文档

简单说下,形如:v-loading.fullscreen="true"这句话,v-loading就是directive,fullscreen就是它的modifier,true就是binding的value值。所以,就是通过这样简单的一句话实现全屏的loading效果,并且当没有fullscreen修饰符的时候就是对拥有该指令的元素进行loading效果。组件通过binding的value值来控制loading的开启和关闭。(类似于v-model的效果)

其实loading也是一个实际的DOM节点,只不过要把它做成一个方便的指令还不是特别容易。

首先我们需要写一下loading的Vue组件。新建一个Loading.vue文件

<template>
  <transition
    name="loading"
    @after-leave="handleAfterLeave">
    <div
      v-show="visible"
      class="loading-mask"
      :class={'fullscreen': fullscreen}>
      <div class="loading">
        ...
      </div>
      <div class="loading-text" v-if="text">
        {{ text }}
      </div>
    </div>
  </transition>
</template>
<script>
export default {
  name: 'loading',
  data () {
    return {
      visible: true,
      fullscreen: true,
      text: null
    }
  },
  methods: {
    handleAfterLeave() {
      this.$emit('after-leave');
    }
  }
}
</script>
<style>
.loading-mask{
  position: absolute; // 非全屏模式下,position是absolute
  z-index: 10000;
  background-color: rgba(255,235,215, .8);
  margin: 0;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  transition: opacity .3s;
}
.loading-mask.fullscreen{
  position: fixed; // 全屏模式下,position是fixed
}
// ...
</style>

Loading关键是实现两个效果:

????1.全屏loading,此时可以通过插入body下,然后将Loading的position改为fixed,插入body实现。
????2.对所在的元素进行loading,此时需要对当前这个元素的的position修改:如果不是absolute的话,就将其修改为relatvie,并插入当前元素下。此时Loading的position就会相对于当前元素进行绝对定位了。
所以在当前目录下创建一个index.js的文件,用来声明我们的directive的逻辑。

import Vue from 'vue'
const LoadingConstructor = Vue.extend(require('./Loading.vue'))

export default {
  install: Vue => {
    Vue.directive('loading', { // 指令的关键
      bind: (el, binding) => {
        const loading = new LoadingConstructor({ // 实例化一个loading
          el: document.createElement('div'),
          data: {
            text: el.getAttribute('loading-text'), // 通过loading-text属性获取loading的文字
            fullscreen: !!binding.modifiers.fullscreen
          }
        })
        el.instance = loading; // el.instance是个Vue实例
        el.loading = loading.$el; // el.loading的DOM元素是loading.$el
        el.loadingStyle = {};
        toggleLoading(el, binding);
      },
      update: (el, binding) => {
        el.instance.setText(el.getAttribute('loading-text'))
        if(binding.oldValue !== binding.value) {
          toggleLoading(el, binding)
        }
      },
      unbind: (el, binding) => { // 解绑
        if(el.domInserted) {
          if(binding.modifiers.fullscreen) {
              document.body.removeChild(el.loading);
          }else {
            el.loading &&
            el.loading.parentNode &&
            el.loading.parentNode.removeChild(el.loading);
          }
        }
      }
    })

    const toggleLoading = (el, binding) => { // 用于控制Loading的出现与消失
      if(binding.value) {
        Vue.nextTick(() => {
          if (binding.modifiers.fullscreen) { // 如果是全屏
            el.originalPosition = document.body.style.position;
            el.originalOverflow = document.body.style.overflow;
            insertDom(document.body, el, binding); // 插入dom
          } else {
            el.originalPosition = el.style.position;
            insertDom(el, el, binding); // 如果非全屏,插入元素自身
          }
        })
      } else {
        if (el.domVisible) {
          el.instance.$on('after-leave', () => {
            el.domVisible = false;
            if (binding.modifiers.fullscreen && el.originalOverflow !== 'hidden') {
              document.body.style.overflow = el.originalOverflow;
            }
            if (binding.modifiers.fullscreen) {
              document.body.style.position = el.originalPosition;
            } else {
              el.style.position = el.originalPosition;
            }
          });
          el.instance.visible = false;
        }
      }
    }

    const insertDom = (parent, el, binding) => { // 插入dom的逻辑
      if(!el.domVisible) {
        Object.keys(el.loadingStyle).forEach(property => {
          el.loading.style[property] = el.loadingStyle[property];
        });
        if(el.originalPosition !== 'absolute') {
          parent.style.position = 'relative'
        }
        if (binding.modifiers.fullscreen) {
          parent.style.overflow = 'hidden'
        }
        el.domVisible = true;
        parent.appendChild(el.loading) // 插入的是el.loading而不是el本身
        Vue.nextTick(() => {
          el.instance.visible = true;
        });
        el.domInserted = true;
      }
    }
  }
}

同样,写完整个逻辑,我们需要将其注册到项目里的Vue下:

// main.js

// ...
import Loading from 'loading/index.js'

Vue.use(Loading)

// ...

至此我们已经可以使用形如

<div v-loading.fullscreen="loading" loading-text="正在加载中">

这样的方式来实现调用一个loading组件了。

总结

在用Vue写我们的项目的时候,不管是写页面还是写形如这样的功能型组件,其实都是一件很有意思的事情。本文介绍的三种调用组件的方式,也是根据实际情况出发而实际操作、实现的。不同的组件通过不同的方式去调用,方便了开发人员,也能更好地对代码进行维护。当然也许还有其他的方式,我并没有了解,也欢迎大家在评论里指出!

最后再次感谢element-ui的源码给予的极大启发。

文章作者: Molunerfinn
文章链接: https://molunerfinn.com/vue-components/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 MARKSZのBlog!

原文地址:https://www.cnblogs.com/wwhhq/p/8283769.html

时间: 2024-10-31 17:55:21

Vue组件的三种调用方式的相关文章

Code-C#-Delegate:委托(delegate)的三种调用方式:同步调用,异步调用,异步回调

ylbtech-Code-C#-Delegate:委托(delegate)的三种调用方式:同步调用,异步调用,异步回调 1.返回顶部 1. 下面为即将被调用的方法: public delegate int AddHandler(int a, int b); public class 加法类 { public static int Add(int a, int b) { Console.WriteLine("开始计算:" + a + "+" + b); Thread.

【JS】Js中函数的三种调用方式

在同一个页面中,函数名必须是唯一的,并且区分大小写.Js中可以通过下面三种方式调用函数: 1. 函数的简单调用 2. 在事件的响应中调用函数 3. 通过连接调用函数 具体是怎么操作的呢,下面一一讲解: 函数的简单调用:函数的定义语句通畅被放在HTML文件的<head>内,而函数的调用语句通常被放在<body>中.如果函数定义之前调用函数,执行将会出错. 语法如下: <head> <script type="text/javascript">

java三种调用方式(同步调用/回调/异步调用)

1:同步调用:一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用 2:回调:一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口: 3:异步调用:一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口 ) 实例2:老师平时学生布置任务后不可能一直等待和监督学生完成,老师通常会告诉学生,任务完成后给他打个电话或者发个信息,那么学生给老师返回结果的过程需要老师信息,这就是一个回调的过程.

SVG与HTML、JavaScript的三种调用方式

一.在HTMl中访问SVG的DOM Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html

Python Django 多对多三种创建方式 form组件 cookie和session

一 表中多对多关系三种创建方式 以Book表和Author表多对多关系例 1.第一种方式:全自动(推荐使用):models.ManyToManyField(to='类名') 优点:不需要你手动创建第三张表 缺点:第三张表不是你手动创建的,字段是固定的无法拓展 class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_place

多对多三种创建方式、forms组件、cookies与session

多对多三种创建方式.forms组件.cookies与session 一.多对多三种创建方式 1.全自动 # 优势:不需要你手动创建第三张表 # 不足:由于第三张表不是你手动创建的,也就意味着第三张表字段是固定的无法做扩展 class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) authors = mode

多对多三种创建方式,forms组件,cookie和session

多对多三种创建方式 1.全自动 优点:不需要自己手动创建第三章表缺点:由于第三张表不是自己创建的,也就是说第三张表字段是固定的没有办法做扩展 class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) authors = models.ManyToManyField(to='Author') class Au

vue.js三种安装方式

Vue.js(读音 /vju?/, 类似于 view)是一个构建数据驱动的 web 界面的渐进式框架.Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件.它不仅易于上手,还便于与第三方库或既有项目整合. 下面介绍三种 Vue.js 的安装方法: 独立版本 我们可以在Vue.js的官网上直接下载vue.js,并在html中通过<script>标签中引用.<script src = ../vue.js> </script> 开发环境不要使用最

JS的三种使用方式/CSS的三种使用方式/JS中的DOM事件模型/JS中匿名函数的书写及调用/媒体查询@media的三种使用方式

一.JS的三种使用方式 1.html标签中内嵌JS(不提倡使用.)                <button onclick="javascript:alert('你真点啊.')" > 有本事点我呀!!!!</button>                                2.HTML页面中直接使用JS:                <script type="text/javascript">