使用vue封装右键菜单插件

简介: 使用vue封装右键菜单插件

前言


上周跟大家分享了如何使用vue的自定义指令实现自定义浏览器右键菜单,大家都觉得挺有意思的,这次我把它做成了插件,上传到了npm仓库。


在做这个插件的过程中,踩了蛮多坑,本文就跟大家分享下我的实现思路以及过程,欢迎各位感兴趣的开发者阅本文。


环境搭建


一开始我是直接用的typescript的tsc命令进行打包的,但是我的插件里用到了vue、scss,发现要把这些文件打包进去需要自己去配webpack。


我记得好久之前,我用Vue CLI 2.x创建项目时,可以选择当前要创建的项目是插件还是web项目,现在用的是Vue ClI 4.x了,在创建项目时没看到有这个选项。


于是,我带着侥幸的心理,去Vue CLI 官网找了一波,还真就被我找到了,它的build指令有个target选项,可以选择将其打包成一个插件,它的具体使用方法:vue-cli-service build。


既然Vue CLI提供了现成的解决方案,那就用它提供的吧。


创建项目


  • 在终端进入你的项目目录,使用create命令创建一个名为vue-right-click-menu-next的项目


vue create vue-right-click-menu-next


  • 在接下来的步骤中,选择自定义配置,选vue3, node-sass, eslint+prettier, typescript这些选项


配置依赖项


项目创建好后,我们删掉CLI初始化时创建的东西,然后修改package.json中的内容。

在package.json中,CLI默认是把vuecore-js放在dependencies下的,我们开发的插件是要给其他开发者引用的,如果我们打包的产物中包含Vue包的话可能会引发各种问题,比如用户可能会在引入我们的包之后会在runtime时创建两个不用的Vue实例,所以vue插件的package.json里一定不能将其放在dependencies中,而是要放在peerDependencies中,表明会从引用者的其他的包中引入相对应的包,而不会在这个包里直接引入。


  • 在package.json中添加下述代码,移除原来dependencies下的依赖。


"peerDependencies": {
    "core-js": "^3.6.5",
    "vue": "^3.0.0"
  }


  • 在devDependencies中添加git提交规范相关依赖
{
    "@commitlint/cli": "^11.0.0",
    "@commitlint/config-angular": "^11.0.0",
    "commitizen": "^4.2.2",
    "cz-conventional-changelog": "^3.3.0",
    "husky": "^4.3.0",
}


  • 添加config和husky配置changelog生成地址和强制编辑器提交代码走我们定义的规范
{
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  },
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
}


  • 最后,在script中添加提交命令与生成changelog的命令
{
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
    "commit": "git-cz"
}


配置打包命令


由文档可知,可以通过vue-cli-service build --target lib --name myLib [entry]命令,将一个单独的入口构建为一个库。


那么,我们就可以在package.json的script标签里添加build命令用以执行插件的打包,代码如下。


  • vueRightMenuPlugin 打包后的文件名
  • src/main.ts 插件的入口文件
{
  "build": "vue-cli-service build --target lib --name vueRightMenuPlugin src/main.ts",
}


由于我们的插件启用了typescript,使用它的默认打包,不会帮我们生成ts声明文件,使用我们插件的开发者项目可能会启用typescript,在引用插件时就会报错声明文件不存在,因此我们需要额外做下述操作:


  • tsconfig.jsonz中添加下述代码,打包时在项目的指定位置自动生成配置文件。
{
    "declaration": true,// 是否生成声明文件
    "declarationDir": "dist/lib",// 声明文件打包的位置
}


  • 创建vue.config.js文件,关闭并行打包的一些相关配置。
module.exports = {
  chainWebpack: config => {
    if (process.env.NODE_ENV === "production") {
      config.module.rule("ts").uses.delete("cache-loader");
      config.module
        .rule("ts")
        .use("ts-loader")
        .loader("ts-loader")
        .tap(opts => {
          opts.transpileOnly = false;
          opts.happyPackMode = false;
          return opts;
        });
    }
  },
  parallel: false
};


做完上述操作后,我们运行打包命令时就能自动生成声明文件了。


强制css内联


当我把插件开发完,测试时发现我引用的组件样式丢了,找了好久问题,最后在CLI的文档中找到了问题所在,他有个css.extract属性,它使用来配置打包时是否将css样式提取到独立的文件中,Default: 生产环境下是 true,开发环境下是 false,我们打包时他默认是true,用户需要单独引入这个样式文件文件。


我们可以通过手动将其设置为false,让其在打包时使用内联样式,这样就能解决样式失效的问题了,我们在vue.config.js中加入下述代码。


module.exports = {
  // 强制css内联
  css: { extract: false }
}


添加库描述


做完上述操作,我们跟打包有关的相关的配置就弄好了,接下来我们在package.json中添加库的相关描述,让npm可以正确识别我们的插件。


  • name 插件名称
  • version 版本号
  • description 插件简述
  • private 是否私有
  • main 库的入口文件位置(打包后的入口文件)
  • types 库的声明文件位置
  • publisher 库发布者
  • repository 仓库信息
  • keywords 关键词,在npm找包时所匹配的关键词
  • author 库作者
  • license 库遵守的开源协议
  • bugs bug反馈地址
  • homepage 库主页


{
  "name": "vue-right-click-menu-next",
  "version": "1.0.0",
  "description": "支持vue3的右键菜单插件",
  "private": false,
  "main": "dist/vueRightMenuPlugin.common.js",
  "types": "dist/lib/main.d.ts",
  "publisher": "magicalprogrammer@qq.com",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/likaia/vue-right-click-menu-next.git"
  },
  "keywords": [
    "vuejs",
    "vue3",
    "vue",
    "rightMenu",
    "右键菜单",
    "vueRightMenu"
  ],
  "author": "likaia",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/likaia/vue-right-click-menu-next/issues"
  },
  "homepage": "https://github.com/likaia/vue-right-click-menu-next#readme",
}


完整的配置文件请移步:package.json


实现思路


上篇文章我们的实现思路是需要vuex来做全局状态管理,控制右键菜单的显隐,这次我们要把它做成插件,再使用vuex的话,使用我们插件的人就需要必须引入vuex才行,那就有点不合适了。


展示组件


经过一番思考后,我有了下述思路:


  1. 将右键菜单做成组件,通过props向组件传值。
  2. 使用createApp来加载组件,向组件内部传值,创建一个组件容器
  3. 创建一个div元素,将刚才的组件容器挂载到这个div元素上


销毁组件


完成上述操作后,我们就实现了让右键菜单显示到指定位置,但是要怎么隐藏它呢,经过一番思考后,我又想到了下述思路:


  1. 将上述加载组件的实现封装成一个函数,将创建的div元素作为返回值。
  2. 在插件全局声明一个变量menuVM,默认声明为null
  3. 指令内部触发右键事件时,调用我们封装的函数,用menuVM去接收其返回值
  4. 此时我们创建一个全局点击事件的监听,如果menuVM不为null,我们就把这个元素移除
  5. 触发右键事件时,如果menuVM不为null,表示它上次点开的右键菜单没关,这样就会出问题,因此我们也需要将其从body中移除


实现过程


分析出实现思路后,接下来我们就着手将其实现吧。


创建右键菜单组件


在项目的src下创建components文件夹,在文件夹下创建right-menu.vue文件,样式和组件内容此处我们就不贴了,这里贴一下组件需要传的参数,完整代码请移步:

right-menu.vue)


<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  name: "right-menu",
  props: {
    rightMenuStatus: String,
    rightMenuTop: String,
    rightMenuLeft: String,
    rightMenuList: Array
  }
});
</script>


封装挂载组件函数


我们可以通过vue3的createApp方法来加载一个组件,并给他传值,然后挂载到某个dom节点上,代码如下:


/**
 * 将组件挂在到节点上
 * @param comp 需要挂载的组件
 * @param prop 向组件传的参数
 */
const creatComp = function(comp: Component, prop: rightMenuAttribute) {
  // 创建组件
  const app = createApp(comp, {
    ...prop
  });
  // 创建一个div元素
  const divEle = document.createElement("div");
  // 将创建的div元素挂载追加至body里
  document.body.appendChild(divEle);
  // 将组件挂载至刚才创建的div中
  app.mount(divEle);
  // 返回挂载的元素,便于操作
  return divEle;
};


在install中注册指令并显示菜单


接下来,我们在插件的install方法中,注册一个vue指令rightClick,拦截它的右键事件,获取组件传过来来的参数,挂载组件,渲染右键菜单。代码如下:


install(app: App): void {
    // 创建指令
    app.directive("rightClick", (el, binding): boolean | void => {
      // 指令绑定元素元素是否存在判断
      if (el == null) {
        throw "右键指令错误:元素未绑定";
      }
      el.oncontextmenu = function(e: MouseEvent) {
        if (menuVM != null) {
          // 销毁上次触发的右键菜单DOM
          document.body.removeChild(menuVM);
          menuVM = null;
        }
        const textArray = binding.value.text;
        const handlerObj = binding.value.handler;
        // 菜单选项与事件处理函数是否存在
        if (textArray == null || handlerObj == null) {
          throw "右键菜单内容与事件处理函数为必传项";
        }
        // 事件处理数组
        const handlerArray = [];
        // 处理好的右键菜单
        const menuList = [];
        // 将事件处理函数放入数组中
        for (const key in handlerObj) {
          handlerArray.push(handlerObj[key]);
        }
        if (textArray.length !== handlerArray.length) {
          // 文本数量与事件处理不对等
          throw "右键菜单的每个选项,都必须有它的事件处理函数";
        }
        // 追加右键菜单数据
        for (let i = 0; i < textArray.length; i++) {
          // 右键菜单对象, 添加名称
          const menuObj: rightMenuObjType = {
            text: textArray[i],
            handler: handlerArray[i],
            id: i + 1
          };
          menuList.push(menuObj);
        }
        // 鼠标点的坐标
        const oX = e.clientX;
        const oY = e.clientY;
        // 动态挂载组件,显示右键菜单
        menuVM = creatComp(rightMenu, {
          rightMenuStatus: "block",
          rightMenuTop: oY + "px",
          rightMenuLeft: oX + "px",
          rightMenuList: menuList
        });
        return false;
      };
    });
}


创建监听销毁组件


当用户点击完右键菜单后,我们需要对组件进行销毁,让其隐藏,因此我们在插件的install创建一个对body的点击监听,然后移除我们挂载的组件,代码如下:


install(app: App): void {
    // 监听全局点击,销毁右键菜单dom
    document.body.addEventListener("click", () => {
      if (menuVM != null) {
        // 销毁右键菜单DOM
        document.body.removeChild(menuVM);
        menuVM = null;
      }
    });
  }


完整代码请移步:main.ts)


发布插件


做完上述操作后,我们的插件就开发完成了,可以打包然后发布到npm仓库了。


终端执行下述命令:


npm publish --access public


插件发布成功:vue-right-click-menu-next


兼容Vue2.x


插件是不兼容Vue2.x的,因为creatApp时Vue3新增的语法,一开始我本来想用Vue2.x的extend来实现组件挂载的,发现Vue3把这个语法舍弃了。


这就造成了我需要写两套插件,维护两个插件。


插件的逻辑层面没有啥区别,只有挂载组件写法的不同,Vue2.x中需要使用下述写法:


/**
 * 将组件挂在到节点上
 * @param comp 需要挂载的组件
 * @param prop 向组件传的参数
 */
const creatComp = function(comp, prop) {
  // 创建组件
  const app = Vue.extend(comp);
  // 创建一个div元素
  const divEle = document.createElement("div");
  // 将创建的div元素挂载追加至body里
  document.body.appendChild(divEle);
  // 将组件挂载至刚才创建的div中, 使用propsData进行传参
  new app({
    propsData: {
      ...prop
    }
  }).$mount(divEle);
  // 返回挂载的元素,便于操作
  return divEle;
};


插件地址:vue-right-click-menu


项目地址


本文中开发的插件代码地址:vue-right-click-menu | vue-right-click-menu-next


在线体验地址:chat-system


写在最后


  • 公众号无法外链,如果文中有链接,可点击下方阅读原文查看😊
相关文章
|
9天前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
vue学习第四章
|
9天前
|
JavaScript 前端开发
vue学习第九章(v-model)
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生,自学前端2年半,正向全栈进发。此篇介绍v-model在不同表单元素中的应用及修饰符的使用,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
vue学习第九章(v-model)
|
9天前
|
JavaScript 前端开发 开发者
vue学习第十章(组件开发)
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文深入讲解Vue组件的基本使用、全局与局部组件、父子组件通信及数据传递等内容,适合前端开发者学习参考。持续更新中,期待您的关注!🎉🎉🎉
vue学习第十章(组件开发)
|
14天前
|
JavaScript 前端开发 UED
vue学习第二章
欢迎来到我的博客!我是一名自学了2年半前端的大一学生,熟悉JavaScript与Vue,目前正在向全栈方向发展。如果你从我的博客中有所收获,欢迎关注我,我将持续更新更多优质文章。你的支持是我最大的动力!🎉🎉🎉
|
14天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端2年半,熟悉JavaScript与Vue,正向全栈方向发展。博客内容涵盖Vue基础、列表展示及计数器案例等,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
|
存储 前端开发 JavaScript
为什么我不再用Vue,改用React?
当我走进现代前端开发行业的时候,我做了一个每位开发人员都要做的决策:选择一个合适的框架。当时正逢 jQuery 被淘汰,前端开发者们不再用它编写难看的、非结构化的老式 JavaScript 程序了。
|
14天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
15天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
15天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
15天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。
下一篇
无影云桌面