教你一个vue小技巧,一般人我不说的

本文由云+社区发表

1. 需求

最近的项目中,需要实现在vue框架中动态渲染带提示框的单选/多选文本框,具体的效果如下图所示,在输入框聚焦时,前端组件通过接收的kv参数渲染出选项,用户点击选项,可以将选择的选项的key拼装到输入框中,同时允许用户自由输入。

由于项目中使用的element-ui,首选考虑使用组件的input和select组件,然而实际使用中发现框架提供的组件不能很好满足此需求。例如,使用带输入建议的input组件,能够实现提示框和单选,但并不能方便地实现多选(重复选择会覆盖输入框内的内容)。

而使用框架提供的select选择器的远程搜索功能,能够实现提示框,也能轻松实现单选与多选,但select组件的内容只能通过用户选择(文本框内容必须包含于提示选项中),不允许用户自由输入文本内容。

再加上设计稿需要实现三列布局,最终的返回结果需要动态拼装选项key值,若对现有的element组件进行改造成本过高,因此,尝试封装带提示框的单选/多选文本框组件,记录下封装过程中组件交互方面遇到的问题。

2. 接口参数设计

组件支持传入6个参数,分别为

  1. size (尺寸,String, medium / small / mini)
  2. value (输入值,String,可以使用sync修饰符实现双向绑定)
  3. opt (选项列表,Array,kv数组形如{key:1, value:xxx})
  4. seperator (分隔符,String,如‘,‘、‘|‘、‘-‘)
  5. multiple (是否支持多选,Boolean)
  6. placeholder (提示,String)

调用方式如下:

<cs-select
  size="mini" // 尺寸
  :value.sync="value" // value
  :opt="optParams.kv" // 选项
  seperator="," // 分隔符
  :multiple="true">
</cs-select>

3. 提示框显示隐藏交互实现

细化上述需求,需要在用户点击输入框(获取焦点)时,显示提示框,在用户点击空白区域时隐藏提示框,点击组件自身时不做任何操作。组件的模板结构如下,通过show变量控制提示框的显示与隐藏,在组件的输入框绑定聚焦和失焦事件: @focus="onfocus"@blur="onblur",在focus时设置this.show为true,blur时为false,由于点击了输入框外的选项元素必然导致输入框失焦从而自动关闭,所有问题的关键在于如何实现点击提示选项而不隐藏提示框。

<template>
  <div>
    <!-- 输入框 -->
    <el-input
      @focus="onfocus
      @blur="onblur>
    </el-input>
    <!-- 提示框 -->
    <div v-if="show && opt.length > 0">
      <el-row>
        <el-col :span="8" v-for="(item, index) in opt" :key="index">
          {{item.value}}
        </el-col>
      </el-row>
    </div>
  </div>
</template>

3.1 尝试方案1: click事件主动聚焦

根据上述需求,毫无疑问联想到可以为选项绑定click事件,调用el-input的focus()方法进行主动聚焦,实现如下,此处使用了vue的ref,通过$ref来查找dom元素。

clickEvent () {
  this.show = true // 设置提示框显示
  this.$refs.input.$el.querySelector(‘input‘).focus() // 设置主动聚焦
}

问题:实际开发过程中发现,每次点击提示选项后,提示框会闪烁一次,原因在于js的事件机制,blur事件先于click事件执行,导致提示框隐藏后再显示,造成闪烁

3.2 尝试方案2: blur事件添加延时器 + 开关变量

由于方案1blur事件先于click事件执行,因此考虑使用settimeout延时器来改变执行时间,实现如下。

blurEvent () {
  setTimeout(() => {
    this.show = false
  }, 200)
}

问题:实际开发过程中发现,延时器延时执行关闭操作,导致输入框获取焦点后,主动关闭了提示框,不再自动打开,不满足需求,因此考虑使用开关变量canClose判断当前是否需要执行关闭,实现如下。

focusEvent () {
  this.show = true
  this.canClose = true // 聚焦时打开开关
},
blurEvent () {
  if (this.canClose) {
    setTimeout(() => {
      this.show = false // 只有开关打开时才执行关闭
    }, 200)
  }
},
clickEvent (key) {
  this.canClose = false // 点击提示选项,关闭开关
  this.show = true
  ...
}

问题:实际开发过程中发现,大多数情况下,提示框能够显示与隐藏,但是当操作较快时,会偶尔出现提示框不能关闭或提前关闭的情况,分析原因在于,延时器期间任何对开关的操作可能导致组件开关状态变化,致使状态紊乱。

3.3 尝试方案3: 不使用blur,关闭方法改为事件委托,动态绑定class

如果关闭不使用blur,而是通过点击事件触发,则不会存在上述时序问题,因此考虑在全局使用事件委托,监听用户的点击事件,通过判断节点特殊class实现提示框关闭,实现如下。

$(‘body‘).on(‘click‘, (event) => {
  this.show = false
})
$(‘body‘).on(‘click‘, className, (event) => {
  this.show = true
})

问题1:事件委托,使用固定的class,当同时渲染多个组件时,无法实现单独管理提示框的开关,因此无法渲染多组件,因此class使用动态绑定,每个组件使用不同的class,实现如下。

问题2:阻止冒泡,如果组件的父容器阻止了冒泡,则无法触发body上绑定的关闭方法,需要针对父容器单独处理。

let randId = Math.round(Math.random()*100000)
this.className = `cs-select-${randId}`
// 单独处理父容器,在父容器上绑定关闭事件
...

改造后的组件表面看起来已经基本可用,实际存在诸多问题:

问题1:组件中对父组件绑定了事件,违反了设计模式的迪米特法则,增加了组件间的耦合,不利于后期维护。

问题2:上述操作只考虑了点击事件的关闭,忽略了其他可能关闭的情况,如使用tab按键切换输入框时也需要能正常显示隐藏提示框。

问题3:绑定事件过多会带来性能隐患甚至导致意想不到的问题发生。

3.4 尝试方案4: onfocus + onblur + mousedown + 开关

由于focus事件先于click事件执行,导致了上述方案1和方案2问题的产生,通过查阅资料可知,mousedown事件先于focus事件执行,因此,使用onfocus + onblur + mousedown + 开关能够很好解决上述执行时序问题,具体实现如下。

focusEvent () {
  this.show = true
  this.canClose = true // 聚焦时打开开关
},
blurEvent () {
  if (this.canClose) {
    this.show = false // 只有开关打开时才执行关闭
  }
},
mousedownEvent (key) {
  this.canClose = false // 点击提示选项,关闭开关
  this.show = true
  this.$refs.input.$el.querySelector(‘input‘).focus()
  ...
}

问题:实际开发中发现,由于组件是动态渲染的,mousedownEvent事件中无法直接获取到当前对象的dom元素this.$refs.xxx,导致自动聚焦失败。

3.5 实现方案

在方案4的基础上,使用nextTick异步更新队列能够解决dom渲染时序问题,具体实现针对方案4稍作修改即可。

$nextTick: 在vue官方深入响应式原理中说明了 vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 \(nextTick,则可以在回调中获取更新后的 DOM,官方示例:<https://cn.vuejs.org/v2/guide/reactivity.html#search-query-sidebar>focusEvent () { this.show = true this.canClose = true // 聚焦时打开开关 }, blurEvent () { if (this.canClose) { this.show = false // 只有开关打开时才执行关闭 } }, mousedownEvent (key) { this.canClose = false // 点击提示选项,关闭开关 this.show = true this.\)nextTick(() => { this.\(refs.input.\)el.querySelector(‘input‘).focus() }) ... }4. 组件数据双向绑定

为了方便组件内数据的处理,传入组件的输入值value会首先被split分解为key数组,然后添加watcher观察器,监听输入值的变化,更新提示框的选中状态,并通过$emit方法同步到父组件中,实现数据的双向绑定,输入值的watch如下所示:

watch: {
  inputVal: {
    handler () {
      let selectArray = this.inputFilter()
      this.inputVal = selectArray.join(this.seperator)
      // 更新选中状态
      this.updateActive()
      // 同步数据
      this.$emit(‘update:value‘, this.inputVal) // 可改为v-model
    },
    immediate: true
  }
}

5. 组件应用与改进

带提示框的单选/多选文本框组件的应用场景较多,典型的场景如封装企业联系人的选择器,用户输入用户名关键词,提示框显示相关联系人,同时允许用户自由输入用户名。

组件还有不少可以改进的地方,例如:

  1. 目前的设计通过监听mousedown来阻止提示框的关闭,很明显不能兼容移动端,可以考虑添加touch事件;
  2. 在css布局方面没有判断用户可见的友好性,在极端情况下可能会超出屏幕范围;
  3. 还不支持slot插槽和动态设置class等。

随着整体项目的迭代可以逐步完善。

此文已由作者授权腾讯云+社区发布



教你一个vue小技巧,一般人我不说的

原文地址:https://www.cnblogs.com/qcloud1001/p/10142125.html

时间: 2024-10-11 04:36:11

教你一个vue小技巧,一般人我不说的的相关文章

[每天一个Linux小技巧] gdb 下一次运行多个命令

一般gdb运行的时候,我们仅仅能输入一个命令. 如: (gdb) c (gdb) bt 假设想运行多个命令怎么办? 能否像bash那样, 使用; 如 ls; ls 结论是不行. 但能够通过gdb 内建函数的的方式来实现: (gdb) def cbt >c >bt 10 >end (gdb) 也能够追加到 $HOME/.gdbinit下, 每次启动gdb后会自己主动载入. [每天一个Linux小技巧] gdb 下一次运行多个命令,布布扣,bubuko.com

[每天一个Linux小技巧] 查看时钟源精度

$ dmesg | grep clock [0.000000] OMAP clocksource: GPTIMER1 at 24000000 Hz [0.000000] sched_clock: 32 bits at 24MHz, resolution 41ns, wraps every 178956ms 这里的精度是41ns [每天一个Linux小技巧] 查看时钟源精度

[每天一个Linux小技巧] gdb 下一次执行多个命令

一般gdb执行的时候,我们只能输入一个命令. 如: (gdb) c (gdb) bt 如果想执行多个命令怎么办? 是否能像bash那样, 使用; 如 ls; ls 结论是不行. 但可以通过gdb 内建函数的的方式来实现: (gdb) def cbt >c >bt 10 >end (gdb) 也可以追加到 $HOME/.gdbinit下, 每次启动gdb后会自动加载. [每天一个Linux小技巧] gdb 下一次执行多个命令

分享一个自控小技巧--小任务--简单却十分的有效

场景,话不多说 记不记得自己想做某件事情,比如每天锻炼,但是时常难以开始? 记不记得自己本来计划好的某段时间用来做某件事情,但是一不小心就沉浸于游戏或者电视剧忘了自拔? 记不记得自己计划好的每天晚上花一小时学习某项技能,但是坚持了三天就抛到脑后难以记起了? 我来分享一个关于自控的小技巧,对别人有没有效果我不知道,反正对多年来想要自控多一些的我反正发生了奇效! 技巧就是设定小任务 这个技巧十分的简单,易于执行.就是 设定几个特变简单的.但是每天都要做的小任务. 比如,如果你想要背单词:可以试试设定

11中javascrip教程教不到的小技巧

在这篇文章中,我将分享11条我认为特别有用的技巧.这篇文章是为初学者准备的,但我希望即使是中级JavaScript开发人员也能在这个列表中找到一些新的东西. 1.过滤唯一值 Set对象类型是在ES6中引入的,配合展开操作...一起,我们可以使用它来创建一个新数组,该数组只有唯一的值. 1 const array = [1, 1, 2, 3, 5, 5, 1] 2 const uniqueArray = [...new Set(array)]; 3 console.log(uniqueArray)

dos界面下执行java文件将错误输出到一个文本小技巧

如果dos下执行java出现错误,把错误记录到一个文档 正确时如图,输出结果为hello,我把String的s改为小写,出现错误,用2>命令输出到error.txt在当前目录就出现了error.txt文件

一个word小技巧

最近在进行word格式重拍的时候发现了一个有些恶心的事,怎么去匹配文档里面所有的中文呢? 后来通过网络搜索发现了答案,在word中的查找和替换中有一个选项,可以使用通配符进行匹配. 当我们使用 ([一-龥])^32^13 的时候就可以匹配到一个中文+空格+换行了. 其中,[一-龥],是匹配中文 ^32和^13分别代表了空格和换行符. 特此记录

回收站的文件删除了怎么恢复?教你一招小技巧

回收站的文件删除了怎么恢复?在用户对文件进行普通删除过后,文件将会在回收站中存储着,未进行任何操作之前都可以进行快速的恢复操作,但是如果清空或者删除了该怎么恢复相关的文件呢?回收站的文件删除了恢复步骤该如何进行? 在对回收站文件进行了删除的操作后,系统判定该文件是永久删除,想要恢复这类文件,一般的方式是不奏效的,恢复的话要借助到专业的数据恢复软件进行,下面就来和各位分享一下具体的操作流程吧: 第一步:在电脑的浏览器上下载[互盾数据恢复软件]的安装包,之后点击[立即安装]来进行后续的安装操作,下载

[每天一个Linux小技巧] 强制让内核按单核模式启动

在启动參数里追加 nosmp nosmp的说明例如以下: nosmp [SMP] Tells an SMP kernel to act as a UP kernel, and disable the IO APIC.  legacy for "maxcpus=0".