浅析vue封装自定义插件

  在使用vue的过程中,经常会用到Vue.use,但是大部分对它一知半解,不了解在调用的时候具体做了什么,因此,本文简要概述下在vue中,如何封装自定义插件。

  在开始之前,先补充一句,其实利用vue封装自定义插件的本质就是组件实例化的过程或者指令等公共属性方法的定义过程,比较大的区别在于封装插件需要手动干预,就是一些实例化方法需要手动调用,而Vue的实例化,很多逻辑内部已经帮忙处理掉了。插件相对于组件的优势就是插件封装好了之后,可以开箱即用,而组件是依赖于项目的。对组件初始化过程不是很熟悉的可以参考这篇博文

    我们从vue源码中,可以看到Vue.use的方法定义如下: 

Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // 已经存在插件,则直接返回插件对象
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    const args = toArray(arguments, 1)
    args.unshift(this)
    // vue插件形式可以是对象,也可以是方法,默认会传递一个Vue的构造方法作为参数
    if (typeof plugin.install === ‘function‘) {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === ‘function‘) {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }

  从上述代码中,我们可以看出,Vue.use代码比较简洁,处理逻辑不是很多。我们常用的Vue.use(xxx),xxx可以是方法,也可以是对象。在Vue.use中,通过apply调用插件方法,传入一个参数,Vue的构造方法。举个栗子,最简单的Vue插件封装如下:

// 方法
function vuePlugins (Vue) {
    Vue.directive(‘test‘, {
        bind (el) {
            el.addEventListener(‘click‘, function (e) {
                alert(‘hello world‘)
            })
        }
    })
}

// 对象
const vuePlugins = {
    install (Vue) {
        Vue.directive(‘test‘, {
            bind (el) {
                el.addEventListener(‘click‘, function (e) {
                    alert(‘hello world‘)
                })
            }
        })
    }
}

  以上两种封装方法都可以,说白了,就是将全局注册的指令封装到一个方法中,在Vue.use时调用。这个比较显然易懂。现在举一个稍微复杂点的例子,tooltip在前端开发中经常会用到,直接通过方法能够调用显示,防止不必要的组件注册引入,如果我们单独封装一个tooltip组件,应该如何封装呢?这种封装方式需要了解组件的初始化过程。区别在于将组件封装成插件时,不能通过template将组件实例化挂载到真实DOM中,这一步需要手动去调用对应组件实例化生命周期中的方法。具体实现代码如下:  

// component
let toast = {
    props: {
        show: {
            type: Boolean,
            default: false
        },
        msg: {
            type: String
        }
    },
    template: ‘<div v-show="show" class="toast">{{msg}}</div>‘
}

  组件初始化过程:

// JavaScript初始化逻辑
// 获取toast构造实例
const TempConstructor = Vue.extend(toast)
// 实例化toast
let instance = new TempConstructor()
// 手动创建toast的挂载容器
let div = document.createElement(‘div‘)
// 解析挂载toast
instance.$mount(div)
// 将toast挂载到body中
document.body.append(instance.$el)
// 将toast的调用封装成一个方法,挂载到Vue的原型上
Vue.prototype.$toast = function (msg) {
    instance.show = true
    instance.msg = msg
    setTimeout(() => {
        instance.show = false
    }, 5000)
}

  组件的定义,和普通的组件声明一致。组件的插件化过程,和普通的组件实例化一致,区别在于插件化时组件部分初始化方法需要手动调用。比如:

    1、Vue.extend作用是组装组件的构造方法VueComponent

    2、new TempConstructor()是实例化组件实例。实例化构造方法,只是对组件的状态数据进行了初始化,并没有解析组件的template,也没有后续的生成vnode和解析vnode

    3、instance.$mount(div)的作用是解析模板文件,生成render函数,进而调用createElement生成vnode,最后生成真实DOM,将生成的真实DOM挂载到实例instance的$el属性上,也就是说,实例instance.$el即为组件实例化最终的结果。

    4、组件中,props属性最终会声明在组件实例上,所以直接通过实例的属性,也可以响应式的更改属性的传参。组件的属性初始化方法如下:

function initProps (Comp) {
  const props = Comp.options.props
  for (const key in props) {
    proxy(Comp.prototype, `_props`, key)
  }
}
// 属性代理,从一个原对象中拿数据
export function proxy (target: Object, sourceKey: string, key: string) {
  // 设置对象属性的get/set,将data中的数据代理到组件对象vm上
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

  从上述可以看出,最终会在构造方法中,给所有的属性声明一个变量,本质上是读取_props中的内容,_props中的属性,会在实例化组件,initState中的InitProps中进行响应式的声明,具体代码如下:

function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  if (!isRoot) {
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== ‘production‘) {
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      defineReactive(props, key, value, () => {
        if (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop‘s ` +
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component‘s prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

  这里会遍历所有订单props,响应式的声明属性的get和set。当对属性进行读写时,会调用对应的get/set,进而会触发视图的更新,vue的响应式原理在后面的篇章中会进行介绍。这样,我们可以通过方法参数的传递,来动态的去修改组件的props,进而能够将组件插件化。

  有些人可能会有疑问,到最后挂载到body上的元素是通过document.createElement(‘div‘)创建的div,还是模板的template解析后的结果。其实,最终挂载只是组件解析后的结果。在调用__patch__的过程中,执行流程是,首先,记录老旧的节点,也就是$mount(div)中的div;然后,根据模板解析后的render生成的vnode的节点,去创建DOM节点,创建后的DOM节点会放到instance.$el中;最后一步,会将老旧节点给移除掉。所以,在我们封装一个插件的过程中,实际上手动创建的元素只是一个中间变量,并不会保留在最后。可能大家还会注意到,插件实例化完成后的DOM挂载也是我们手动挂载的,执行的代码是document.body.append(instance.$el)。

  附:test.html 测试代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    .toast{
      position: absolute;
      left: 45%;
      top: 10%;
      width: 10%;
      height: 5%;
      background: #ccc;
      border-radius: 5px;
    }
  </style>
  <title>Hello World</title>
  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
</head>
<body>
<div id=‘app‘ v-test>
  <button @click="handleClick">我是按钮</button>
</div>
<script>
function vuePlugins (Vue) {
    Vue.directive(‘test‘, {
        bind (el) {
            el.addEventListener(‘click‘, function (e) {
                alert(‘hello world‘)
            })
        }
    })
}
// const vuePlugins = {
//     install (Vue) {
//         Vue.directive(‘test‘, {
//             bind (el) {
//                 el.addEventListener(‘click‘, function (e) {
//                     alert(‘hello world‘)
//                 })
//             }
//         })
//     }
// }
Vue.use(vuePlugins)
let toast = {
    props: {
        show: {
            type: Boolean,
            default: false
        },
        msg: {
            type: String
        }
    },
    template: ‘<div v-show="show" class="toast">{{msg}}</div>‘
}
// 获取toast构造实例
const TempConstructor = Vue.extend(toast)
// 实例化toast
let instance = new TempConstructor()
// 手动创建toast的挂载容器
let div = document.createElement(‘div‘)
// 解析挂载toast
instance.$mount(div)
// 将toast挂载到body中
document.body.append(instance.$el)
// 将toast的调用封装成一个方法,挂载到Vue的原型上
Vue.prototype.$toast = function (msg) {
    instance.show = true
    instance.msg = msg
    setTimeout(() => {
        instance.show = false
    }, 5000)
}
var vm = new Vue({
    el: ‘#app‘,
    data: {
        msg: ‘Hello World‘,
        a: 11
    },
    methods: {
        test () {
            console.log(‘这是一个主方法‘)
        },
        handleClick () {
            this.$toast(‘hello world‘)
        }
    },
    created() {
        console.log(‘执行了主组件上的created方法‘)
    },
})
</script>
</body>
</html>

原文地址:https://www.cnblogs.com/gerry2019/p/12158463.html

时间: 2024-08-04 06:21:21

浅析vue封装自定义插件的相关文章

Vue 开发自定义插件学习记录 -- 入门

首先,你需要了解插件实现的基本原理 插件基本原理: 我们都知道用Vue.use注册插件,那你知道Vue.use(plugin) 干了什么? 以下是我对Vue官网的一些摘录和个人的理解 Vue.use( plugin ) 参数: {Object | Function} plugin 用法: 安装 Vue.js 插件.如果插件是一个对象,必须提供 install 方法.如果插件是一个函数,它会被作为 install 方法. install 方法调用时,会将 Vue 作为参数传入. 该方法需要在调用

vue 封装一个插件

1.创建一个vue组件button/button.vue <template> <div class="btn" :style="{'background':color,'color':fontColor}"> {{text}} </div> </template> <script> export default { name:"button", //配置项 props:{ text:{

vue 封装自定义组件

先说说组件的结构 最好单独放一个文件夹,有依赖的话装依赖 Sjld.vue 内容 <template id="sjld"> <label > <select v-model="selected" prop="selected" > <option v-for="option in sheng" :value="option.id" :label="opti

vue中自定义组件(插件)

vue中自定义组件(插件) 原创 2017年01月04日 22:46:43 标签: 插件 在vue项目中,可以自定义组件像vue-resource一样使用Vue.use()方法来使用,具体实现方法: 1.首先建一个自定义组件的文件夹,比如叫loading,里面有一个index.js,还有一个自定义组件loading.vue,在这个loading.vue里面就是这个组件的具体的内容,比如: <template> <div> loading.............. </div

vue封装插件并发布到npm上

vue封装插件并发布到npm上 项目初始化 首先,要创建项目,封装vue的插件用webpack-simple很合适,vue init webpack-simple 项目名称此命令创建我们的项目的目录,创建文件夹和文件,最后结构是这样的  然后根据需求,实现具体功能,这里我封装了一个轮播图组件mini-sliders,主要功能写在lib/slider.vue组件中 功能写好后,我们要写index.js来封装组件 import VueComment from './VueComment.vue' c

vue自定义插件及使用

一.Vue插件有什么用 插件通常会为 Vue 添加全局功能.所谓全局:即不需要像组件那样,每次使用他前都需要先引入一次.对于插件只要在最开始引入一次,在任何组件就可以直接使用.(类似于我们在window上添加的方法属性那样,任何地方都可以用)插件能实现的功能没有限制,不过常见下面几种: 通过插件,添加全局方法或者属性通过插件,添加全局资源:指令/过滤器/过渡等通过插件(使用全局 mixin 方法),添加一些组件选项通过插件,添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上

Vue.js自定义指令的用法与实例

市面上大多数关于Vue.js自定义指令的文章都在讲语法,很少讲实际的应用场景和用例,以致于即便明白了怎么写,也不知道怎么用.本文不讲语法,就讲自定义指令的用法. 自定义指令是用来操作DOM的.尽管Vue推崇数据驱动视图的理念,但并非所有情况都适合数据驱动.自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的DOM操作,并且是可复用的. 比如谷歌图片的加载做得非常优雅,在图片未完成加载前,用随机的背景色占位,图片加载完成后才直接渲染出来.用自定义指令可以非常方便的实现这个功能. 效果: 自定义

vue中自定义指令

在vue中自定义标签,首先要调用vue中一个directive的方法,具体方法:Vue.direction('指令名称',function(){ }); 例如我们要写一个关于颜色的指令,叫v-colorred: 1 Vue.directive('colorred',function(){ 2 3 this.el.style.color='red'; 4 }); 在html中,我直接用v-colorred指令就可以了,例如: 1 <p v-colorred>1234567890</p>

ionic2 调用自定义插件之研究

摘要 最近在研发一个移动项目,架构已经定型,使用Ionic2开发,虽然ionic2 已经提供了ionic native插件,但是当遇到一些特别的需求大多时候还是需要我们自己封装插件. cordova机制我在此就不提了,我们使用Typescript调用cordova plugin就如同调用第三方库是一个道理,那么这里就少不了书写declare文件,下面我就把几种封装调用的几种方式介绍一下. 方式一,编写全局declare文件 现在我们有一个“残峰(我的同事)”封装好的签名版插件,cordova p