谈VUE防抖节流最佳解决方案(功能部件)
前言
有使用电子海图经验的同学可能遇到过这样的场景。在window.onresize事件的回调中触发echartsBox.resize()方法,达到重绘的目的。Resize事件是连续触发的,这意味着e-Charts实例会连续重绘,这非常消耗性能。还有一种常见的场景是在输入标签的输入事件中请求后端接口,输入事件也是连续触发的。如果我输入“12”,我会两次请求“1”和“12”的接口参数,这比浪费网络资源更糟糕。如果参数为“1”的请求比参数为“12”的接口晚返回数据,那么我们得到的数据与预期不一致。当然很多包都可以基于axios来做,最后一个请求可以通过拦截来取消或者处理,但是从防抖开始就比较简单了。
什么是防抖和节流
功能去反弹
说明:事件连续触发时,如果在一定时间间隔内没有再次触发,事件处理功能只会执行一次。如果在设定的时间间隔到来之前再次触发事件,延迟将重新开始。
案例:连续触发滚动事件时,不立即执行手柄功能,1000毫秒内不触发滚动事件时,延时触发手柄功能一次。
函数去抖(fn,wait) {让timeout=null返回函数(){ if(timeout!==null) clearTimeout(超时)timeout=setTimeout(fn,wait);} }函数句柄(){ console . log(math . random())} window . addEventListener(' scroll ',去抖(handle,1000))addEventListener的第二个参数实际上是去抖函数中的返回方法。当在AddEventListener中执行一次触发器事件时,将不会执行line let timeout=null。然后,每次触发滚动事件时,最后一个延迟将被清除,同时记录一个新的延迟。当滚动事件停止触发时,最后记录的延迟不会被清除,并且可以被延迟。这是去抖功能的原理
功能油门(油门)
说明:当事件连续触发时,每隔一个时间间隔定期执行事件处理功能。
案例:当滚动事件被连续触发时,句柄功能不会立即执行,而是每1000毫秒执行一次。
函数throttle(fn,delay){ var prev=date . now()return function(){ var now=date . now()} if(now-prev delay){ fn()} prev=date . now()} }函数handle(){ console . log(math . random())} window . addeventlistener(' scroll ',throttle (handle,1000))在原理上类似于防抖。每次执行fn函数时,prev都会更新,以记录执行的时间。当触发下一个事件时,判断时间间隔是否达到预设设置,重复上述操作。
防抖和节流可用于鼠标移动、滚动、调整大小、输入和其他事件。它们之间的区别在于,防抖将仅在连续事件周期结束时执行一次,而节流将在事件周期中定期执行几次。
在vue实习
在vue中实现防抖只有以下两种方法
封装实用工具封装组件封装实用工具
通过修改上述情况,可以打包一个简单的utils工具
utils.js
让timeout=nullfunction去抖(fn,wait) { if(timeout!==null)cleartime out(time out)time out=setTimeout(fn,wait)}导出默认去抖app.js
input type=' text ' @ input=' debook input($ event)' import debook from '。/utils ' export default { methods : {去抖输入(e){去抖(()={console.log (e.target.value)},1000)}
至于组件的打包,我们需要使用$listeners和$ attrs的属性。都是vue2.4的新内容,官网介绍比较晦涩。让我们看看他们是做什么的:
$ listenerss:的父组件在绑定子组件时会将很多属性绑定到子组件,然后在子组件中用道具注册它们,那么那些没有用道具注册的属性就会放在$ listener中,当然不包括类和样式,可以通过v-bind='$attrs '传递给子组件的内部组件。
$ listeners:父组件在子组件上绑定的事件,没有。本机修饰符将放在$listeners中,它可以通过v-on='$listeners '传递到内部组件中。
简单地说,$listeners和$ attrs是属性和事件的继承者,这对于组件的二次封装非常有用。
我们以element-ui的el-input组件为例,封装了一个带防抖的去抖输入组件
去抖输入
template El-input v-bind=' $ attrs ' @ input=' DEBOUNeInput '//template script export default { data(){ return { timeout : null } },methods : { DEBOUNeInput(value){ if(this . time out!==null)clear time out(this . time out)this . time out=setTimeout(()={ this。$emit('input ',value) },1000) } }}/scriptapp.vue
模板去抖-输入占位符='防抖'前缀-图标=' El-icon-search ' @ input=' input eve '/去抖-输入/templatescript import去抖input from '。/DEBOUND-input ' export default { methods : { input eve(value){ console . log(value)} },components 3360 { DEBOUND input } }/script。上述组件用$ attrs封装。虽然开发者不需要关注属性的传递,但是使用起来很不方便,因为el-input被封装在里面,限制了风格。接触过react高阶组件的学生可能知道react高阶组件本质上是React组件,其功能通过包传入。经过一系列的处理,最终返回一个相对增强的React组件。那么我们能从vue的这个想法中学习到什么呢?让我们来看看vue的功能组件。
关于vue功能组件
什么是功能组件?
功能组件是指用一个只接受某种道具的函数来渲染一个vue组件。我们可以将这种组件标记为功能性的,这意味着它没有状态(没有响应数据)和实例(没有这个上下文)。
功能组件如下所示:
export default()={ function : true,props 3360 {//props可选}。//为了弥补缺失的实例,提供第二个参数作为context render3360函数(createelement,Context) {return vNode }}注意:在2.3.0之前的版本中,如果功能组件想要接收prop,props选项是必需的。在2.3.0或更高版本中,您可以省略props选项,所有组件上的功能都将自动隐式解析为props。但是一旦你注册了道具,只有注册的道具才会出现在上下文中
render函数的第二个参数context用于替换上下文this,它是一个具有以下字段的对象:
Props:提供所有props的children: VNode子节点的数组槽:的函数返回对象scope slot 3360(2 . 6 . 0),它公开了传入的作用域槽。普通插槽也作为函数公开。Data:传递给组件的整个数据对象被传递给组件父级:对父组件listener 3360(2 . 3 . 0)的引用,该对象包含父组件为当前组件注册的所有事件侦听器。这是数据的别名。在注入时: (2.3.0)如果使用注入选项,对象包含应该注入的属性。虚拟机里有什么?$插槽应用编程接口
插槽用于访问插槽分发的内容。每个命名槽都有其相应的属性(例如,v-slot:foo中的内容将在vm中找到。$slots.foo)。默认属性包括未包含在命名插槽中的所有节点,或者v-slot:default的内容。
插槽()和子插槽之间的比较
你可能想知道为什么你需要插槽()和孩子。不是插槽()。默认类似孩子?在某些场景中,它是——,但是如果它是以下带有子节点的功能组件呢?
my-functional-component p v-slot : foo first/p PS second/p/my-functional-component对于这个组件孩子们会给你两个段落标签,而插槽()。系统默认值只会传递第二个匿名段落标签,插槽()。富(中国姓氏)会传递第一个具名段落标签。同时拥有孩子们和插槽(),因此你可以选择让组件感知某个插槽机制,还是简单地通过传递孩子们,移交给其它组件去处理。
一个函数式组件的使用场景
假设有一个a组件,引入了a1、a2、a3三个组件,a组件的父组件给a组件传入了一个类型属性根据类型的值a组件来决定显示a1、a2、a3中的那个组件。这样的场景a组件用函数式组件是非常方便的。那么为什么要用函数式组件呢?一句话:渲染开销低,因为函数式组件只是函数。
用函数式组件的方式来实现防抖
因为业务关系该防抖组件的封装同时支持输入、按钮、el输入、el按钮的使用,如果是投入类组件对投入事件做防抖处理,如果是按钮类组件对点击事件做防抖处理。
常数去抖=(有趣,延迟=500,之前)=}让定时器=null返回(参数)={ timer window。cleartime out(定时器)在(参数)定时器=窗口之前。settimeout(()={//点击事件乐趣是功能输入事件乐趣是数组if(!数组。I array(fun)){ fun=[fun]} for(让我在乐趣中){ fun[i](params) } timer=null },parsent(delay))} }导出默认值{ name: '去抖',functional: true,//静态组件当不声明功能的时该组件同样拥有上下文以及生命周期函数render(createElement,context){ const before=context。道具。const time=context之前。道具。time const vnodeList=context。插槽().默认if (vnodeList===未定义){ console.warn('去抖组件必须要有子元素)返回null } const vnode=vnodeList[0]| | null/获取子元素虚拟DOM if(vnode。标记==' input '){ const DefaultFun=vnode。数据。打开。输入常量DEBOUND fun=DEBOUND(DefaultFun,时间,之前)//获取节流函数vnode。数据。打开。input=DEBOUND fun } else if(vnode。标记==' button '){ const Defaultfun=vnode。数据。打开。单击const DEBOUND fun=DEBOUND(Defaultfun,时间,之前)//获取节流函数vnode。数据。打开。click=DEBOUND fun } else if(vnode。组件选项vnode。组件选项。标记==' El-input '){ const Defaultfun=vnode。组件选项。听众。输入常量DEBOUND fun=DEBOUND(Defaultfun,时间,之前)//获取节流函数vnode。组件选项。听众。input=DEBOUND fun } else if(vnode。组件选项vnode。组件选项。tag==' El-button '){ const default fun=vnode。组件选项。听众。单击const DEBOUND fun=DEBOUND(默认fun,时间,之前)//获取节流函数vnode。组件选项。听众。click=DEBOUND fun } else { console。warn(' DEBOUND组件内只能出现下面组件的任意一个且唯一el按钮、el输入、按钮、输入)返回vnode }返回vnode }模板去抖时间=' 300 ' :before=' beforeFun '输入类型=' text ' v-model=' in model ' @ input=' input change '/去抖/templatescriptimport去抖从./debake ' export default { data(){ return { in model : 1 } },methods : { InputChange(e){ console。日志(例如目标。值)'防抖)},前趣(e){控制台。日志(例如目标。值,'不防抖)} },组件3360 {去抖} }/脚本原理也很简单就是在虚拟节点中拦截在下面的点击、输入事件做防抖处理,这样在使用上就非常简单了。
自定义指令管理的
我们来思考一个问题,函数式组件封装防抖的关节是获取vNode,那么我们通过自定义指令同样可以拿到vNode,甚至还可以得到原生的多姆,这样用自定义指令来处理会更加方便。
相关阅读
$ attrsor $ listeners https://cn.vuejs.org/v2/api/#vm-attrs功能组件https://cn.vuejs.org/v2/guide/render-function.html#功能组件自定义指令https://cn.vuejs.org/v2/guide/custom-directive.html
以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。
版权声明:谈VUE防抖节流最佳解决方案(功能部件)是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。