Vue插件实现过程中遇到的问题总结

目录
  • 场景介绍
  • 插件实现
  • 问题一、重复的头部组件
  • 问题二、另一种实现思路
  • 问题三、是否可以不使用Vue.extend
  • 总结

场景介绍

最近做H5遇到了一个场景:每个页面需要展示一个带有标题的头部。一个实现思路是使用全局组件。假设我们创建一个名为TheHeader.vue的全局组件,伪代码如下:

<template>
    <h2>{{ title }}</h2>
</template>

<script>
export default {
props: {
    title: {
        type: String,
        default: ''
    }
}
}
</script>

创建好全局组件后,在每个页面组件中引用该组件并传入props中即可。例如我们在页面A中引用该组件,页面A对应的组件是A.vue

<template>
    <div>
        <TheHeader :title="title" />
    </div>
</template>
<script>
    export default {
        data() {
            title: ''
        },
        created(){
            this.title = '我的主页'
        }
    }
</script>

使用起来非常简单,不过有一点美中不足:如果头部组件需要传入的props很多,那么在页面组件中维护对应的props就会比较繁琐。针对这种情况,有一个更好的思路来实现这个场景,就是使用Vue插件。

同样是在A.vue组件调用头部组件,使用Vue插件的调用方式会更加简洁:

<template>
    <div />
</template>
<script>
    export default {
        created(){
            this.$setHeader('我的主页')
        }
    }
</script>

我们看到,使用Vue插件来实现,不需要在A.vue中显式地放入TheHeader组件,也不需要在A.vue的data函数中放入对应的props,只需要调用一个函数即可。那么,这个插件是怎么实现的呢?

插件实现

它的实现具体实现步骤如下:

  1. 创建一个SFC(single file component),这里就是TheHeader组件
  2. 创建一个plugin.js文件,引入SFC,通过Vue.extend方法扩展获取一个新的Vue构造函数并实例化。
  3. 实例化并通过函数调用更新Vue组件实例。

按照上面的步骤,我们来创建一个plugin.js文件:

import TheHeader from './TheHeader.vue'
import Vue from 'vue'

const headerPlugin = {
    install(Vue) {
        const vueInstance = new (Vue.extend(TheHeader))().$mount()
        Vue.prototype.$setHeader = function(title) {
            vueInstance.title = title
            document.body.prepend(vueInstance.$el)

        }
    }
}
Vue.use(headerPlugin)

我们随后在main.js中引入plugin.js,就完成了插件实现的全部逻辑过程。不过,尽管这个插件已经实现了,但是有不少问题。

问题一、重复的头部组件

如果我们在单页面组件中使用,只要使用router.push方法之后,我们就会发现一个神奇的问题:在新的页面出现了两个头部组件。如果我们再跳几次,头部组件的数量也会随之增加。这是因为,我们在每个页面都调用了这个方法,因此每个页面都在文档中放入了对应DOM。

考虑到这点,我们需要对上面的组件进行优化,我们把实例化的过程放到插件外面:

import TheHeader from './TheHeader.vue'
import Vue from 'vue'

const vueInstance = new (Vue.extend(TheHeader))().$mount()
const headerPlugin = {
    install(Vue) {
        Vue.prototype.$setHeader = function(title) {
            vueInstance.title = title
            document.body.prepend(vueInstance.$el)

        }
    }
}
Vue.use(headerPlugin)

这样处理,虽然还是会重复在文档中插入DOM。不过,由于是同一个vue实例,对应的DOM没有发生改变,所以插入的DOM始终只有一个。这样,我们就解决了展示多个头部组件的问题。为了不重复执行插入DOM的操作,我们还可以做一个优化:

import TheHeader from './TheHeader.vue'
import Vue from 'vue'

const vueInstance = new (Vue.extend(TheHeader))().$mount()
const hasPrepend = false
const headerPlugin = {
    install(Vue) {
        Vue.prototype.$setHeader = function(title) {
            vueInstance.title = title
            if (!hasPrepend) {
                document.body.prepend(vueInstance.$el)
                hasPrepend = true
            }

        }
    }
}
Vue.use(headerPlugin)

增加一个变量来控制是否已经插入了DOM,如果已经插入了,就不再执行插入的操作。优化以后,这个插件的实现就差不多了。不过,个人在实现过程中有几个问题,这里也一并记录一下。

问题二、另一种实现思路

在实现过程中突发奇想,是不是可以直接修改TheHeader组件的data函数来实现这个组件呢?看下面的代码:

import TheHeader from './TheHeader.vue'
import Vue from 'vue'

let el = null
const headerPlugin = {
    install(Vue) {
        Vue.prototype.$setHeader = function(title) {
            TheHeader.data = function() {
                title
            }
            const vueInstance = new (Vue.extend(TheHeader))().$mount()
            el = vueInstance.$el
            if (el) {
                document.body.removeChild(el)
                document.body.prepend(el)
            }

        }
    }
}
Vue.use(headerPlugin)

看上去也没什么问题。不过实践后发现,调用$setHeader方法,只有第一次传入的值会生效。例如第一次传入的是'我的主页',第二次传入的是'个人信息',那么头部组件将始终展示我的主页,而不会展示个人信息。原因是什么呢?

深入Vue源码后发现,在第一次调用new Vue以后,Header多了一个Ctor属性,这个属性缓存了Header组件对应的构造函数。后续调用new Vue(TheHeader)时,使用的构造函数始终都是第一次缓存的,因此title的值也不会发生变化。Vue源码对应的代码如下:

Vue.extend = function (extendOptions) {
    extendOptions = extendOptions || {};
    var Super = this;
    var SuperId = Super.cid;
    var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
    if (cachedCtors[SuperId]) { // 如果有缓存,直接返回缓存的构造函数
      return cachedCtors[SuperId]
    }

    var name = extendOptions.name || Super.options.name;
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name);
    }

    var Sub = function VueComponent (options) {
      this._init(options);
    };
    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;
    Sub.cid = cid++;
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    );
    Sub['super'] = Super;

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    if (Sub.options.props) {
      initProps$1(Sub);
    }
    if (Sub.options.computed) {
      initComputed$1(Sub);
    }

    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend;
    Sub.mixin = Super.mixin;
    Sub.use = Super.use;

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type];
    });
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub;
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options;
    Sub.extendOptions = extendOptions;
    Sub.sealedOptions = extend({}, Sub.options);

    // cache constructor
    cachedCtors[SuperId] = Sub; // 这里就是缓存Ctor构造函数的地方
    return Sub
  }

找到了原因,我们会发现这种方式也是可以的,我们只需要在plugin.js中加一行代码

import TheHeader from './TheHeader.vue'
import Vue from 'vue'

let el = null
const headerPlugin = {
    install(Vue) {
        Vue.prototype.$setHeader = function(title) {
            TheHeader.data = function() {
                title
            }
            TheHeader.Ctor = {}
            const vueInstance = new Vue(TheHeader).$mount()
            el = vueInstance.$el
            if (el) {
                document.body.removeChild(el)
                document.body.prepend(el)
            }

        }
    }
}
Vue.use(headerPlugin)

每次执行$setHeader方法时,我们都将缓存的构造函数去掉即可。

问题三、是否可以不使用Vue.extend

实测其实不使用Vue.extend,直接使用Vue也是可行的,相关代码如下:

import TheHeader from './TheHeader.vue'
import Vue from 'vue'

const vueInstance = new Vue(TheHeader).$mount()
const hasPrepend = false
const headerPlugin = {
    install(Vue) {
        Vue.prototype.$setHeader = function(title) {
            vueInstance.title = title
            if (!hasPrepend) {
                document.body.prepend(vueInstance.$el)
                hasPrepend = true
            }

        }
    }
}
Vue.use(headerPlugin)

直接使用Vue来创建实例相较extend创建实例来说,不会在Header.vue中缓存Ctor属性,相较来说是一个更好的办法。但是之前有看过Vant实现Toast组件,基本上是使用Vue.extend方法而没有直接使用Vue,这是为什么呢?

总结

到此这篇关于Vue插件实现过程中遇到问题的文章就介绍到这了,更多相关Vue插件实现问题内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue 右键菜单插件 简单、可扩展、样式自定义的右键菜单

    今天分享的不是技术,今天给大家分享个插件,针对现有的vue右键菜单插件,大多数都是需要使用插件本身自定义的标签,很多地方不方便,可扩展性也很低,所以我决定写了一款自定义指令调用右键菜单(vuerightmenu) 安装  npm install rightmenu --save-dev   开始 //main.js import vue from "vue"; import rightMenu from "rightMenu"; vue.use(rightMenu)

  • Vue.js 插件开发详解

    前言 随着 Vue.js 越来越火,Vue.js 的相关插件也在不断的被贡献出来,数不胜数.比如官方推荐的 vue-router.vuex 等,都是非常优秀的插件.但是我们更多的人还只停留在使用的阶段,比较少自己开发.所以接下来会通过一个简单的 vue-toast 插件,来了解掌握插件的开发和使用. 认识插件 想要开发插件,先要认识一个插件是什么样子的. Vue.js 的插件应当有一个公开方法 install .这个方法的第一个参数是 Vue 构造器 , 第二个参数是一个可选的选项对象: MyP

  • Vue项目安装插件并保存

    比如安装jszip插件的命令行如下: npm install jszip --save-dev 只有执行了--save-dev 才会将当前安装的插件版本保存在package.json文件中 总结 以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持.如果你想了解更多相关内容请查看下面相关链接

  • Vue插件写、用详解(附demo)

    Vue插件 1.概述 简单来说,插件就是指对Vue的功能的增强或补充. 比如说,让你在每个单页面的组件里,都可以调用某个方法,或者共享使用某个变量,或者在某个方法之前执行一段代码等 2.使用方法 总体流程应该是: [声明插件]--[写插件]--[注册插件]--[使用插件] 写插件和声明插件是同步的,然后注册到Vue对象中(不用担心重复注册),最后在写Vue组件的时候使用写的插件 声明插件 先写一个js文件,这个js文件就是插件文件,里面的基本内容如下: /* 说明: * 插件文件:service

  • vue3.0实现插件封装

      最近公司有一个新的项目,项目框架是我来负责搭建的,所以果断选择了Vue3.x+ts.vue3.x不同于vue2.x,他们两的插件封装方式完全不一样.由于项目中需要用到自定义提示框,所以想着自己封装一个.vue2.x提供了一个vue.extend的全局方法.那么vue3.x是不是也会提供什么方法呢?果然从vue3.x源码中还是找到了.插件封装的方法,还是分为两步. 1.组件准备   按照vue3.x的组件风格封装一个自定义提示框组件.在props属性中定义好.需要传入的数据流. <templa

  • vue自定义全局组件(自定义插件)的用法

    有时候我们在做开发的时候,就想自己写一个插件然后就可以使用自己的插件,那种成就感很强.博主最近研究element-ui和axios的时候,发现他们是自定义组件,但是唯一有一点不同的是,在用element-ui的时候是使用Vue.use()语句来使用的,而axios的时候,不用Vue.use(),只要import就可以导入进来了,感觉很神奇,细细的发现,原来他们的不同是因为axios里面并没有写install方法,而element-ui就有写这个方法,下面就利用这个install来写一个自己的插件

  • Vue插件实现过程中遇到的问题总结

    目录 场景介绍 插件实现 问题一.重复的头部组件 问题二.另一种实现思路 问题三.是否可以不使用Vue.extend 总结 场景介绍 最近做H5遇到了一个场景:每个页面需要展示一个带有标题的头部.一个实现思路是使用全局组件.假设我们创建一个名为TheHeader.vue的全局组件,伪代码如下: <template> <h2>{{ title }}</h2> </template> <script> export default { props:

  • Vue组件传值过程中丢失数据的分析与解决方案

    前言 在上一篇文章 JavaScript 中的两种数据类型中,分别介绍了基本类型和引用类型,以及引用类型的浅拷贝与深拷贝.这里需要注意的是,该文章中深拷贝引用类型值的方法,并不是完美的,引用类型值中的某些属性值,仍不能完整地复制到新的变量中.比如函数值,在深拷贝过程中,就会丢失. 问题 在实际项目中,假如使用了二次封装的组件,并且封装的组件内部做了一些属性值的深拷贝操作,就有极有可能因为传入的属性值是引用类型的值,导致丢失部分数据. 举例 以基于 el-table 封装的 ak-table 组件

  • Jenkins初级使用过程中的异常处理

    目录 一.在使用插件Invoke Phing targets的时候 二.使用publish over ssh的时候的错误 在使用Jenkins一些基本功能的时候,或者说是基本插件的时候,会遇到各种各样的报错.这里就设想模拟一下,重现一下以前遇到过的问题,记录一下.虽说是Jenkins使用过程中出现这样的问题,但实际上可以把这种思路应用在运维其他问题的排查逻辑上面.这种分享也符合我们的技术积累信条,欲成大事,比以史为鉴. 一.在使用插件Invoke Phing targets的时候 1.报错: j

  • 解决vue.js在编写过程中出现空格不规范报错的问题

    找到build文件夹下面的webpack.base.conf.js文件. 然后打开该文件,找到图下这段代码,把他注释掉. 注释掉之后,再进行子页面等编写的时候,空格不规范的情况下也不会再报错啦.因为这个报错对于初学者来说实在头大.哈哈O(∩_∩)O哈哈~ 我标注的这些地方,原本是有严格的空格规范要求的,这些报错真是另人烦躁呀o(╥﹏╥)o 反正我把这个问题解决了,特别开心哒哒哒~~~ 以上这篇解决vue.js在编写过程中出现空格不规范报错的问题就是小编分享给大家的全部内容了,希望能给大家一个参考

  • 解决新建一个vue项目过程中遇到的问题

    我就废话不多说了,大家还是直接看代码吧~ /usr/local/bin/node /usr/local/lib/node_modules/npm/bin/npm-cli.js run dev --scripts-prepend-node-path=auto > mytodolists@1.0.0 dev /Users/chenqiurui/WebstormProjects/myVue > webpack-dev-server --inline --progress --config build

  • 安装node.js以及搭建vue项目过程中遇到的问题详解

    目录 一.node.js安装 二.如何找node.js历史版本 1.点击DOWNLOADS 2.点击页面下方 3.翻页找到历史版本 三.检查是否安装成功? 四.安装成功后需要配置环境变量: 五.环境搭建 六.项目创建 总结 一.node.js安装 进入官网 https://nodejs.org/en/download/ 直接点击下载安装!安装过程直接下一步就行: 二.如何找node.js历史版本 (https://nodejs.org/en/download/) 1.点击DOWNLOADS 2.

  • 实例教学如何写vue插件

    在学习之前,先问问自己,为什么要编写vue的插件. 在一个项目中,尤其是大型项目,有很多部分需要复用,比如加载的loading动画,弹出框.如果一个一个的引用也稍显麻烦,而且在一个vue文件中引用的组件多了,会显得代码臃肿,所以才有了封装vue插件的需求. 说完需求,就来看看具体实现.目前我尝试了两种不一样的插件编写的方法,逐个介绍. 这是我的项目目录,大致的结构解释这样,尽量简单,容易理解. 一个是loading插件,一个是toast插件,不同的地方在于:loading插件是作为组件引入使用,

  • 使用vue cli4.x搭建vue项目的过程详解

    cli-4.x已经发布好久了,斟酌了好久,还是决定将原来的cli-2.x升级到4.x,详细的升级过程可以戳这里 1.创建项目 vue create vuetest 2.选择配置方式 ? Please pick a preset: (Use arrow keys) ☜(使用箭头键) > default (babel, eslint) ☜(使用默认的配置,会安装babel和eslint) Manually select features ☜(手动配置) 这里我选择的是手动配置(使用↑ ↓箭头切换,E

  • Vue单页面应用中实现Markdown渲染

    之前渲染 Markdown 的时候, 笔者使用的是 mavonEditor 的预览模式, 使用起来比较爽, 只需要引入组件即可, 但是在最近的开发中, 遇到了困难. 主要问题在于作为单页面应用, 站内链接必须是使用 router-link 跳转, 如果使用 mavonEditor 默认渲染的 a 标签, 就会重新加载页面, 用户体验较差. 动态渲染 想要实现在前端动态地根据用户内容渲染router-link , 需要使用动态渲染, 根据 官方文档, 直接修改vue.config.js 即可: /

随机推荐