Vue3指令——搜索框输入防抖实现
点击小卡片,参与粉丝专属福利!!大家好,我是Lvzl[1], 一个三年工作经验的前端小菜鸡,在掘金[2]平台分享一些 平时学习的感悟 & 实际项目场景 的文章。
前言「搜索??」这个场景在各种业务的系统中都是是非常常见的,比如电商平台的商品搜索、百度搜索、掘金搜索、google搜索......,只要是有内容呈现给用户的,都少不了搜索功能。
按照触发搜索动作的不同可分为:
输完关键字后手动触发搜索输入字符自动搜索第一种是由用户去决定何时进行搜索,没啥问题。
第二种是通过监听用户录入的事件自动搜索,自动搜索能在一定程度上提升用户体验(比如在用户输入时关键词联想提示),同时也可能会带来问题,因为input事件触发是非常频繁的,比如下面这个示例:
打开百度的搜索,找到输入框的dom,然后绑定一个input事件,看看我们录入一个telephone会触发多少次input事件:
temp1.addEventListener('oninput',(val)=console.log(val))
触发了10次:那如果每触发一次都会调用后台接口查询,那当用户量足够大,就会给后台服务器带来很大的压力,导致接口响应慢,甚至引起服务器宕机,也会给用户带来不好的体验。
为了不引起过多请求给服务端造成压力,我们期望在用户在输入完以后再发起请求,那怎么判断用户是否输入完了?
可以认为当用户在一个固定的时间内都没有录入字符是输入完成/暂时输入完成。这个是老生常谈的知识点了————「防抖」和「节流」。掘金有很多文章专门写这个的,笔者在此不过多描述,简单一笔带过说下笔者的看法。
防抖 & 节流防抖防抖的函数在调用后,如果在防抖时间内没有再次触发,就会在过了 防抖时间 后执行;如果在防抖时间内再次触发,不论触发多少次,执行的时机会一直往后延迟,直到满足最后一次调用时间 小于 当前时间减去防抖时间才会执行。
functiondebounce(fn,time){
lettimer
returnfunction(...argu){
if(timer){
clearTimeout(timer)
timer=null
}
timer=setTimeout(()={
fn(...argu)
clearTimeout(timer)
timer=null
},time)
}
}
节流节流函数在调用后,在设置的节流时间后执行一次,在此期间,不论触发多少次,都直接return了。
functionthrottle(fn,time){
letflag=true
returnfunction(...argu){
if(!flag){
return
}
flag=false
lettimer=setTimeout(()={
fn(...argu)
flag=true
clearTimeout(timer)
timer=null
},time)
}
}
再来看下加了防抖 & 节流 处理的input事件执行情况:
防抖:
防抖 + 节流:
从上面的两个图不难看出防抖 和节流的区别,节流的函数如果一直被触发,每隔一段时间执行一次。而防抖则是一直延迟,最后执行一次。
输入防抖存在的问题上面的防抖函数执行貌似没啥问题,前提是我们输入的是英文,如果是中文输入就出问题了,看下图:
1.gif当中文输入的时候,即使已经防抖处理了,还可能会执行多次。接下来咱们进入正题了,如何实现输入防抖Vue3指令(并解决中文输入触发多次的问题)。
指令实现为什么要以指令的方式实现?我能想到的有以下两点:
将防抖处理的逻辑封装在指令内部重用,更易用。指令要支持input输入框,也要支持封装input的组件,也就是说需要进行底层 DOM 访问的逻辑,而这一点恰好是指令提供的能力之一。需要补充Vue 指令相关知识的同学点这里自定义指令
期望使用方式:
template
input v-model="val" v-debounceInput:600="onInput" /
/template
script setup lang="ts"
import { ref } from 'vue'
const val = ref('')
function onInput(e: Event): void {
console.log(e)
}
/script
即指令的绑定值为 执行逻辑,指令参数为 防抖时长。
指令实现:
import{debounce,isFunction}from'../../utils/index'
letinputFunction:(event:Event)={}
//由于要同时支持 input 输入框和封装 input 的组件,因此需要去找到input这个元素。
functionfindInput(el:HTMLElement):HTMLElement|null{
constquene:HTMLElement[]=[]
quene.push(el)
while(quene.length0){
constcurrent=quene.shift()
if(current?.tagName==='INPUT'){
returncurrent
}
if(current?.childNodes){
quene.push(...current.childNodes)
}
}
returnnull
}
exportdefault{
mounted(el:HTMLElement,binding:any){
const{value,arg}=binding
if(valueisFunction(value)){
lettimeout=600
if(arg!Number.isNaN(arg)){
timeout=Number(arg)
}
inputFunction=debounce(value,timeout)//执行函数防抖处理
constinput=findInput(el)
el._INPUT=input
if(input){
input.addEventListener('input',inputFunction)
}
}
},
beforeUnmount(el:HTMLElement){
if(el._INPUT){
el._INPUT.removeEventListener('input',inputFunction)
el._INPUT=null
}
}
}
接着处理中文输入可能会触发多次的问题。可借助compositionstart和compositionend来实现。
输入法编辑器开始新的输入合成时会触发compositionstart 事件。例如,当用户使用拼音输入法开始输入汉字时,这个事件就会被触发。当文本段落的组成完成或取消时,compositionend 事件将被触发 (具有特殊字符的触发,需要一系列键和其他输入,如语音识别或移动中的字词建议)。在一开始拼音输入法时就设置composing为true,因此在compositionEnd没有调用之前都不会执行函数,等compositionEnd调用,通过dispatchEvent派发一个input事件出去,保证逻辑一定会执行。
functioncompositionStart(event:CompositionEvent){
event.target.composing=true
}
functioncompositionEnd(e:CompositionEvent){
e.target.composing=false
constevent=newEvent('input',{bubbles:true})
e.target?.dispatchEvent(event)
}
exportdefault{
mounted(el:HTMLElement,binding:any){
//...
if(input){
input.addEventListener('input',inputFunction)
input.addEventListener('compositionstart',compositionStart)
input.addEventListener('compositionend',compositionEnd)
}
//...
},
beforeUnmount(el:HTMLElement){
if(el._INPUT){
el._INPUT.removeEventListener('input',inputFunction)
el._INPUT.removeEventListener('compositionstart',compositionStart)
el._INPUT.removeEventListener('compositionend',compositionEnd)
}
}
}
防抖实现 & 判断是 function
exportfunctiondebounce(input:(event:Event)=any,timeout:number):(this:HTMLElement,ev:Event)=any{
lettimer:string|number|NodeJS.Timeout|undefined
return(event:Event)={
//@ts-ignore
if(event.target.composing===true){
return
}
if(timer){
clearTimeout(timer)
timer=undefined
}
timer=setTimeout(()={
input(event)
clearTimeout(timer)
timer=undefined
},timeout)
}
}
exportfunctionisFunction(param:any):boolean{
returnObject.prototype.toString.call(param)==='[objectFunction]'
}
注册指令(全局注册):
createApp(App).directive('debounceInput',debounceInput).mount('#app')
写个案例测试一下:
template
div/div
input v-model="val" v-debounceInput="onInput" /
div/div
input v-model="count" type="text" @input="onInput" /
/template
script setup lang="ts"
import { ref } from 'vue'
const val = ref('')
const count = ref('')
function onInput(e: Event): void {
console.log('调用了onInput')
}
/script
看下最终的效果:
1.gif差别还是非常明显的。指令已发布npm仓库,有需要可自取。
总结??感谢浏览,通过本文你可以了解到以下内容:
防抖节流Vue3指令开发compositionstart 事件 (需要注意浏览器兼容性)compositionend 事件(需要注意浏览器兼容性)
如果文章对你有帮助的话欢迎「关注+点赞+收藏」
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线