深入理解使用Vue实现Context-Menu的思考与总结

简介

先来看最终成果:

操作逻辑为:

  • 点击 ... 弹出 context-menu;
  • 点击非 context-menu 区域,隐藏 context-menu;
  • 点击 context-menu 中的任何一个选项,隐藏 context-menu;

思考

项目是基于 vux 做的,本想着偷懒直接在 vux 库翻组件用,但看了一圈下来,居然这么通用的组件在 vuex 中没有!接着又去翻开源的解决方案,看了几个库还算 ok,但此时前端小哥来了,说实现这个菜单不需要用到这么重的东西,直接写就行了。

当时我的脑海中在思考了把 context-menu 封装成一个 component ,通过数据配置的方式动态拓展菜单选项。但没想到前端小哥直接给我干了回来,没必要进行封装,这个组件对页面依赖性太强,就算封装完了下次也不一定能直接用,PM 的思路又这么清奇。

所以,最后的做法就直接硬上了。

实现

调整操作逻辑

该页面是一个通俗意义上的列表展示页,使用了 vux 的 swipeout 表单组件,给用户提供了侧滑操作,需要把原先写好的侧滑功能删除。

调整 UI

在调整 UI 的过程中我感到了 CSS 满满的恶意,当然说是这么说,但实际上还是因为太久没有用而导致的不够熟悉。非常费劲的终于调整了好了新 UI,此时已经过去了整整一天了,非常怀念 autoLayout 。

context-mune

在正式开始写之前,上文已经说了我一直在翻开源库,主要是不懂得如何下手去写。距离上一次写 vue 已经过去快两个月了,而且也没搞清楚如何写一个组件,所以中间有一段时间浪费在了这上。最后的解决思路让我感到意外:

<div class="more-menu-wrapper">
 <ul v-show="item.showOption">
  <li>更换分类</li>
  <li>向上移动</li>
  <li>移至顶部</li>
  <li>取消收藏</li>
 </ul>
</div>

没想到使用无序列表就可以完成了~在 iOS 中,我会在 UITableView 和 UIStackView 中纠结。当然只有这样是不行的,当又调整了 UI 后,发现 ... 和 context-menu “融合”在了一起,没有设计图中的“悬浮”效果,最后的解决方法是:

.more-wrapper {
 /* ... */
 position: absolute;
 .more-menu-wrapper {
  position: relative;
  /* ... */
 }
}

当继续调整 CSS 时又发现 context-menu 的会被其父组件挡住, context-menu 的显示范围会限制于其父组件的显示高度,最后得知是 overflow 这个属性在最底层的父组件中设置了 overflow: hidden; ,删除掉,使其为默认的 visible 即可显示为 context-menu 高度溢出的效果。

事件绑定

UI 都调整完后开始绑定事件。因为只是改造 UI,并没有涉及到多少的新逻辑,所以很快的就写出了以下代码:

<ul v-show="item.showOption">
 <li @click="moveItem(item)">更换分类</li>
 <li @click="moveUp(item)">向上移动</li>
 <li @click="setTop(item)">移至顶部</li>
 <li @click="deleteItem(item)">取消收藏</li>
</ul>

context-menu 的显示依赖 v-show ,当页面首次拉取到网络数据时, data 中对每个 listData 的 item 新增了 context-menu 显示隐藏的初始化标志位 item.showOption = false ,且在这四个入口方法中都控制了 item.showOption 的改变:

//...
moveUp(item) {
 item.showOption = false;
 // ...
}
//...

刷新页面,很愉快的看到了 context-menu 的显示,但在点击菜单选项时没有任何反应!一开始以为是标志位的问题,但看来看去没有任何问题。

本来想去找前端小哥看一眼,但一直不在工位上,最后问了下同组的前端实习生,他认为是 item.showOption 字段在数据更新时没有加上,导致后续直接读取时不存在。

但我其实一直纳闷如果 item.showOption 字段数据不存在的话,那第一次的页面渲染实际上是有错误的。我们两个人看了一会也没发现具体是哪有问题,最后只能四处寻找前端小哥,没想到他已经被封闭起来做商业化了......

前端小哥在文件中加上了 debugger 进行调试,发现进入到 moveUp 等一类事件时虽然 item.showOption 被修改成功了,一旦出去事件周期外,又被改回去了。

最后发现,问题出在被 冒泡 到了父组件中,调用了 ... 所绑定的 onMore 事件中,而在 onMore 事件中 item.showOption = true ,所以实际上是执行了 context-menu 和 ... 的两者所绑定的事件。解决的方法是:

<ul v-show="item.showOption">
 <li @click.stop="moveItem(item)">更换分类</li>
 <li @click.stop="moveUp(item)">向上移动</li>
 <li @click.stop="setTop(item)">移至顶部</li>
 <li @click.stop="deleteItem(item)">取消收藏</li>
</ul>

使用 @click.stop 来阻止冒泡事件。解决完问题后,前端小哥还好奇我做 iOS 怎么会不知道冒泡事件的问题,但实际上在 iOS 中跟前端的思路完全是反过来的。iOS 的事件响应链是逐级传递到子组件中,也就是向下传递,而不是像前端中的向上传递。所以在遇到这个问题时也就完全没有往冒泡的方面去思考。

触摸其它区域消失 context-menu

在 iOS 中,我会直接封装出一个带有 UIWindow 的组件。与 context-menu 有关的所有操作与主 window 没有任何关系,更别说事件穿透了。所以最终我的做法是多加了一个遮罩层,显示和隐藏的时机与 context-menu 的时机保持一致。

最后在我拿着最终的成果去找前端小哥复查时,他对这个做法不满意,还是觉得要使用 outside-click 的做法。也就是使用 js 中的事件代理,通过 e.targe 去判断。最后找到了可以使用 v-outside-click 进行。关于后续的细节就不展开了, v-outside-click 的使用也十分简洁。

在使用 v-outside-click 这个库的过程中遇到了一个比较大的问题。v-outside-click 此库给我的感觉是用于单个组件,而不适用于多个组件。列表中的每一个 cell 都需要带上一个单独的 context-menu,如果给每一个 context-menu 都绑上一个单独的 outside-click 事件,一旦用户的触摸范围不在 context-menu 中,则视图上的所有 context-menu 都会响应这个 outside-click 事件,列表数据一旦多起来,事件响应次数将线性增长。

这个问题跟前端小哥说过后,他说问题不大,那就这样吧~接下来的问题就到了怎么在 outside-click 事件中标识出是哪个 context-menu 需要隐藏呢?刚开始就按照了以往的套路,直接使用了如下所示的方式:

<div v-click-outside="onClickOutside(item)">
 <!-- ... -->
</div>

然后开心看到了报错 Binding value must be a function or an object。提示需要传入一个方法?!翻了源码后发现了这么一段:

function processDirectiveArguments(bindingValue) {
 const isFunction = typeof bindingValue === 'function'
 if (!isFunction && typeof bindingValue !== 'object') {
 throw new Error('v-click-outside: Binding value must be a function or an object')
 }

 // ...
}

回过头去看之前写的代码,没有问题啊!思来想去还是没弄明白,又去找了前端小哥请求帮忙,经过了一番折腾了,他的结论是这个库应该是有问题的。最后采取的解决方法是:

<div v-click-outside="onClickOutside">
 <p>…</p>
 <!-- 重点 -->
 <div :id="item.metricId" v-show="item.showOption">
  <ul>
   <li>更换分类</li>
   <!-- ... -->
  </ul>
 </div>
</div>
onClickOutside (event, el) {
 let queryInstance = el.querySelector('.more-menu-wrapper')
 if (queryInstance) {
  let metricId = el.querySelector('.more-menu-wrapper').id;
  if (metricId != "") {
   this.listData.some((item) => {
    if (item.metricId == metricId) {
     item.showOption = false;
     return true;
    }
   });
  }
 }
}

通过设置 context-menu 的 id 作为标识,然后在 v-outside-click 的指令方法中获取 id,通过这个 id 去数据源中找到对应的 item,从而设置 item.showOption = false 来隐藏 context-menu。

总结

这算是转大前端完成的第一个功能吧,因为不熟悉导致中间出现了一些好玩的事情。客户端和前端的开发流程说大也不大,但要是说没有是绝对不可能的。在一些小的问题上,没有踩过坑或者没有大佬带一带,真的会爬不起来或者就弃坑了,说到底其实还是需要多加学习啊!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 深入理解使用Vue实现Context-Menu的思考与总结

    简介 先来看最终成果: 操作逻辑为: 点击 ... 弹出 context-menu; 点击非 context-menu 区域,隐藏 context-menu: 点击 context-menu 中的任何一个选项,隐藏 context-menu: 思考 项目是基于 vux 做的,本想着偷懒直接在 vux 库翻组件用,但看了一圈下来,居然这么通用的组件在 vuex 中没有!接着又去翻开源的解决方案,看了几个库还算 ok,但此时前端小哥来了,说实现这个菜单不需要用到这么重的东西,直接写就行了. 当时我的

  • 关于Vue.js一些问题和思考学习笔记(2)

    前言 本文非vue教程,仅为学习vue过程中的个人理解与笔记,有说的不正确的地方欢迎指正讨论 1.computed计算属性函数中不能使用vm变量 在计算属性的函数中,不能使用Vue构造函数返回的vm变量,因为此时vm还未返回,依然处于Vue内部构造函数过程中,遂只能使用this来代替vm. 若要使用typescript,可使用以下方法来实现代码智能感知 vm = vm || this; 另:其他不能用vm变量,只能使用this变量的地方,都可以通过此方法来获得Typescript的智能感知和代码

  • 关于Vue.js一些问题和思考学习笔记(1)

    前言 本文不是Vue.js的教程,只是一边看官网Vue的教程文档一边记录并总结学习过程中遇到的一些问题和思考的笔记. 1.vue和avalon一样,都不支持VM初始时不存在的属性 而在Angular里是可以支持的,因为angular采用脏检查的方式实现双向绑定,vue和avalon都是采用setter和getter实现双向绑定 例,如下代码在一秒后不会显示出"xxcanghai"的字样 <div id="app"> <h1>{{obj.tex

  • vue axios 简单封装以及思考

    axios 简介 axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征: -------------------------------------------------------------------------------- •从浏览器中创建 XMLHttpRequest •从 node.js 发出 http 请求 •支持 Promise API •拦截请求和响应 •转换请求和响应数据 •取消请求 •自动转换JSON数据 •客户端支

  • 浅谈vue后台管理系统权限控制思考与实践

    前言 最近在开发管理系统时遇到了任何管理系统都会有的需求---权限控制,之前也遇到过这种需求,但是架构不完善导致的各种问题使得后期维护非常麻烦,这一次的方案解决了之前的种种问题,现做一次记录,当然这个架构后期可能会有坑,不过得一步一步的尝试才能发现并解决问题. 权限控制需求 因为是单页面应用,路由交给前端来控制,对于一些需要特定权限才能查看的信息的保护变得尤为重要,如果前端不做好权限校验,后端也一时疏忽,就可能就会导致数据泄露. 对于权限控制,需求大致为如下: 对于大模块的限制,比如需要通过路由

  • vue项目中使用require.context引入组件

    目录 背景 require.context 是什么? require.context 的基本用法 require.content 的应用场景 背景 我们在vue项目中,我们可能或有很多的组件需要全局注册,大家有没有遇到这样的烦恼,组件太多,需要一个一个引入注册呢? require.context 是什么? require.context 是webpack的api,我们可以通过require.context()函数来创建自己的context. require.context 的基本用法 语法如下

  • Vue3 封装 Element Plus Menu 无限级菜单组件功能的详细代码

    目录 1 数据结构定义 1.1 菜单项数据结构 1.2 菜单配置数据结构 2 使用 tsx 实现封装 2.1 tsx 基本结构 2.2 定义 prop 2.3 递归实现组件 3 使用 SFC 实现菜单封装 3.1 封装菜单项的渲染 3.2 封装菜单组件 4 测试组件 4.1 菜单测试数据 4.2 测试页面 4.3 运行效果 总结: 本文分别使用 SFC(模板方式)和 tsx 方式对 Element Plus el-menu 组件进行二次封装,实现配置化的菜单,有了配置化的菜单,后续便可以根据路由

  • vue服务端渲染操作简单入门实例分析

    本文实例讲述了vue服务端渲染操作.分享给大家供大家参考,具体如下: 想到要学习vue-ssr的同学,自不必多说,一定是熟悉了vue,并且多多少少做过几个项目.然后学习vue服务端渲染无非解决首屏渲染的白屏问题以及SEO友好. 话不多说,笔者也是研究多日才搞明白这个服务端渲染到底是杂么回事!!! 一,首先实现下官网的基本案例 随便建一个目录,然后执行npm init初始化项目,生成工程文件package.json:创建server.js:然后按照vue-ssr官方链接:https://ssr.v

  • vue中自定义右键菜单插件

    前言: 作为一个刚刚入门前端的搬砖工作者,写博客只是为了能够记录自己因为业务使用过的一些插件,为了后续更好的使用和改造 本文分享了vue中自定义右键菜单插件的具体代码,供大家参考,具体内容如下 演示 用法 通过npm安装插件 npm i vue-context -S 在main.js中引入并注册 import Vue from 'vue'; import VueContext from 'vue-context'; new Vue({   components: {     VueContext

  • Vue中this.$nextTick()的具体使用

    目录 1.官网说法 2.DOM 更新 3.获取更新后的 DOM 4.小结:this.$nextTick()的使用场景 1.官网说法 在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 核心点有2个:DOM 更新和获取更新后的 DOM. DOM 更新,可以理解为:Vue 的DOM更新机制,那 Vue 的DOM更新机制是什么? 2.DOM 更新 首先,我们要知道:Vue实现响应式并不是数据发生变化之后DOM立即变化,而是异步执行DOM更新的. 通过代

随机推荐