当前位置: 首页 > 科技观察

在Vue中使用装饰器,我是认真的

时间:2023-03-14 01:08:52 科技观察

作为一个曾经的Java码农,当我第一眼看到js中的装饰器(Decorator)时,立马想到了Java中的注解。当然,在实践中,从原理和作用上来说,Java注解与JS装饰器还是有很大区别的。这篇文章的标题是Vue中装饰器的使用。我是认真的,但本文将从装饰器的概念发展开始。让我们来看看。通过本文的内容,您将学到以下内容:理解什么是装饰器。在方法中使用装饰器。在类中使用装饰器。在Vue中使用装饰器,想换工作,关注公众号,我每天带你刷大厂面试题,关注===大厂offer。什么是装饰器Decorator是ES2016提出的一个提案,目前处于Stage2,装饰器的体验可以点击https://github.com/tc39/proposal-decorators查看详情。装饰器是一种与类相关的语法糖,用于包装或修饰类的行为或类的方法。装饰器其实就是设计模式中装饰器模式的一种实现。但是,上面说的概念太干了。让我们用人类的语言来翻译它并举个例子。在日常开发和写bug的过程中,我们经常会用到防抖和节流,比如下面的classMyClass{follow=debounce(function(){console.log('我是子君,关注我')},100)}constmyClass=newMyClass()//多次调用只会输出一次myClass.follow()myClass.follow()以上是防抖的例子。我们通过debounce函数包裹另外一个函数来实现防抖这时候还有一个需求。例如,我们想在调用follow函数前后打印一条日志。这个时候我们也可以开发一个日志功能,然后继续包裹follow/***最外层是防抖的,不然会多次调用日志*/classMyClass{follow=debounce(log(function(){console.log('我是子君,关注我')}),100)}上面代码中的debounce和log这两个函数本质上是两个wrapper函数。通过这两个函数对原函数的包装,改变了原函数的行为,js中装饰器的原理是这样的。上面我们使用装饰器代码修改为classMyClass{@debounce(100)@logfollow(){console.log('我是子君,关注我')}}装饰器的形式为@+函数名,如果有参数,下面括号内可以传参数。装饰器可以用在方法上。装饰器可以应用于类或者类中的属性,但是一般来说应用于类属性的场景会比较多,比如我们上面提到的log,Debounce等一般都是应用于类属性。接下来,让我们看看如何实现一个装饰器并将其应用到一个类中。在实现装饰器之前,我们需要了解属性描述符。当我们在对象中定义属性时,属性上其实有很多属性描述符。这些描述符表示属性可以不能修改,可以枚举,可以删除等。同时,ECMAScript将这些属性描述符分为两类,分别是数据属性和访问器属性,以及数据属性和访问器属性不能共存。数据属性数据属性包含可以读取和写入值的数据值的位置。数据属性包含四个描述符,分别可配置表示是否可以通过delete删除该属性,是否可以修改该属性的其他描述符特性,或者是否可以将数据属性作为访问器属性进行修改。当我们通过letobj={name:''}声明一个对象时,这个对象中所有属性的可配置描述符的值为true2.enumerable表示是否可以通过forin或者Object.keys等获取。属性,我们一般声明的对象中这个描述符的值为true,但是对于class类中的属性,这个值为false3.writable表示属性的数据值是否可以修改。通过修改这个为false,可以实现属性只读的效果。4.value表示当前属性的数据值。读取属性值时,从这里读取;写属性值的时候,会写到这个位置。访问器属性访问器属性不包含数据值,它们包含两个函数getter和setter,而configurable和enumerable是数据属性和访问器属性共享的两个描述符。1.getter在读取属性的时候调用这个函数,默认函数是undefined2.setter在写属性值的时候调用这个函数,默认函数是undefined理解了这六个描述符之后,你可能会有几个问题:如何我如何定义和修改这些属性描述符?这些属性描述符与今天文章的主题有什么关系?那么是时候揭晓答案了。使用过Object.defineProperty了解vue2.0双向绑定原理的同学一定知道,Vue的双向绑定是通过使用Object.defineProperty来定义数据属性的getter和setter方法来实现的。比如有一个对象letobj={name:'子君',officialAccounts:'前端玩的一些东西'}希望这个对象中的用户名不能被修改。如何使用Object.defineProperty定义它?Object.defineProperty(obj,'name',{//设置writable为false,该属性不会被修改writable:false})//修改obj.nameobj.name="gentleman"//打印还是子君控制台。log(obj.name)通过Object.defineProperty来定义或修改对象属性的属性描述符,但是由于data属性和accessor属性是互斥的,所以一次只能修改其中一个,需要注意。定义一个debounce装饰器装饰器本质上还是一个函数,只是这个函数的参数是固定的,下面是debounce装饰器的代码/***@paramwaitdelaytime*/functiondebounce(wait){returnfunction(target,name,descriptor){descriptor.value=debounce(descriptor.value,wait)}}//如何使用classMyClass{@debounce(100)follow(){console.log('我是子君,我的公众号是[前端要玩的一些东西],注意惊喜')}}下面我们逐行分析代码。首先,我们定义一个debounce函数和一个参数wait。这个函数对应下面调用装饰器时使用的函数。@debounce(100)debounce函数返回一个新的函数,它是装饰器的核心。这个函数有三个参数。下面针对目标一一分析:这个类的属性函数挂载在谁身上?比如上面的例子对应MyClass类名:这个类的属性函数名,对应上面的followdescriptor:这就是我们前面说的属性描述符。通过直接描述上面的属性,可以实现属性只读和数据重写的功能。3.然后三行descriptor.value=debounce(descriptor.value,wait),我们前面已经知道属性描述符上面的值对应的是这个属性的值,所以我们通过重写的方式把这个属性用debounce函数包裹起来,这样当函数调用跟随时,包装的函数实际上被调用了。通过以上三步,我们实现了可以在类属性上使用的装饰器,并将其应用到类属性上。在类上使用装饰器Decorator不仅可以应用于类的属性,也可以直接应用于类。比如我希望实现一个类似Vue混入的功能,将一些方法属性混入一个类中,应该怎么做?//这个是要混入Objectconstmethods={logger(){console.log('log')}}//这是一个登录注销类classLogin{login(){}logout(){}}如何把上面的方法混进去Login,首先我们实现一个类装饰器函数mixins(obj){returnfunction(target){Object.assign(target.prototype,obj)}}//然后混入@mixins(methods)classLogin{login(){}logout(){}}这样类装饰器就实现了。对于类装饰器来说,只有一个参数,即target,它对应于类本身。了解了装饰器之后,我们再来看看如何在Vue中使用装饰器。在Vue中使用装饰器使用ts开发Vue的同学一定对vue-property-decorator不陌生。本插件提供了很多装饰器,方便大家在开发时使用。当然,本文的中点不是这个插件。其实如果我们的项目不用ts,我们也可以用装饰器,怎么用呢?配置基础环境除了一些老项目,我们一般在新建Vue项目时,都会选择使用脚手架vue-cli3/4来新建。这时候新建的项目已经默认支持装饰器了,不需要额外配置太多。东西,如果你的项目使用了eslint,那么你需要为eslint配置如下。parserOptions:{ecmaFeatures:{//SupportdecoratorslegacyDecorators:true}}使用装饰器虽然Vue组件,我们在写的时候一般会导出一个对象,但这并不影响我们直接在组件中使用装饰器。例如,以上例中的日志为例。functionlog(){/***@paramtarget对应方法的对象*@paramname对应属性方法的名称*@paramdescriptor对应属性方法的修饰符*/returnfunction(target,name,descriptor){console.log(target,name,descriptor)constfn=descriptor.valuedescriptor.value=function(...rest){console.log(`这是调用方法【${name}】之前打印的日志`)fn.call(this,...rest)console.log(`这是调用方法【${name}】后打印的日志`)}}}exportdefault{created(){this.getData()},methods:{@log()getData(){控制台。log('getdata')}}}看完上面的代码,你有没有发现在Vue中使用装饰器是非常简单的,和类属性上使用的方法是一模一样的,但是有一点是要付出代价的注意,在methods方法上使用了装饰器。这时候装饰器的目标对应的就是方法。除了在方法上使用装饰器之外,您还可以在生命周期钩子函数上使用装饰器。此时target对应的是整个组件对象。一些常用的装饰器下面小编列举几个小编在项目中常用的装饰器,方便大家使用1.功能节流和防抖功能节流和防抖应用场景比较广,一般用在Sometimes要调用的函数将通过throttle或debounce方法进行封装。现在你可以使用上面提到的内容将这两个函数封装成一个装饰器。使用lodash提供的方法进行防抖节流。也可以自己使用实现节流防抖功能import{throttle,debounce}from'lodash'/***functionthrottlingdecorator*@param{number}waitthrottlingmilliseconds*@param{Object}options节流选项对象*[options.leading=true](boolean):指定应在节流开始之前进行调用。*[options.trailing=true](boolean):指定应在节流结束后进行调用。*/exportconstthrottle=function(wait,options={}){returnfunction(target,name,descriptor){descriptor.value=throttle(descriptor.value,wait,options)}}/***函数防抖装饰器*@param{number}等待需要延迟的毫秒数。*@param{Object}optionsoptionobject*[options.leading=false](boolean):指定在延迟开始前调用。*[options.maxWait](number):设置func允许延迟的最大值。*[options.trailing=true](boolean):指定在延迟结束后调用。*/exportconstdebounce=function(wait,options={}){returnfunction(target,name,descriptor){descriptor.value=debounce(descriptor.value,wait,options)}}打包后,在组件中使用import{debounce}from'@/decorator'exportdefault{methods:{@debounce(100)resize(){}}}2.加载数据时,为了给用户一个友好的提醒,防止用户继续操作,会一般是在Display请求前加载一次,请求结束后再关闭加载。一般写法如下');}finally{loading.clear()}}}}我们可以将上面的加载逻辑用装饰器重新包装一下,如下代码import{Toast}from'vant'/***loadingdecorator*@param{*}message提示信息*@param{function}errorFn异常处理逻辑*/exportconstloading=function(message='Loading...',errorFn=function(){}){returnfunction(target,name,descriptor){constfn=descriptor.valuedescriptor.value=asyncfunction(...rest){constloading=Toast.loading({message:message,forbidClick:true})try{returnawaitfn.call(this,...rest)}catch(error){//当调用失败,用户自定义失败回调函数,执行errorFn&&errorFn.call(this,error,...rest)console.error(error)}finally{loading.clear()}}}}然后修改上面的组件代码exportdefault{methods:{@loading('loading')asyncgetData(){try{constdata=awaitloadData()//其他操作}catch(error){//异常处理Toast.fail('loadingfailed');}}}}3.确认框点击删除按钮时,一般需要弹出a提示框让用户确认是否删除。这时候常规的写法可能是这样cannotberolledback'}).then(()=>{console.log('deletehere')})}}}我们可以把上面确认的流程提出来,做成装饰器,如下代码import{Dialog}from'vant'/***确认提示框装饰器*@param{*}消息提示信息*@param{*}titletitle*@param{*}cancelFncancel回调函数*/exportfunctionconfirm(message='Areyousureyouwantto删除数据,此操作不可回滚。',title='Prompt',cancelFn=function(){}){returnfunction(target,name,descriptor){constoriginFn=descriptor.valuedescriptor.value=asyncfunction(...rest){try{awaitDialog.confirm({message,title:title})originFn.apply(this,rest)}catch(error){cancelFn&&cancelFn(error)}}}}然后在使用确认框的时候,可以使用exportdefault{methods:{//可以不传参数,使用默认参数@confirm()deleteData(){console.log('deletehere')}}}是不是瞬间就简单多了,当然还可以继续封装很多很多装饰器,因为内容文章有限,这三款暂时有货。装饰器的组合上面我们在类属性上使用装饰器的时候说装饰器可以组合使用,在Vue组件上使用也是如此。比如我们希望确认删除后,调用接口时会出现loading,那么可以这样写(一定要注意顺序)exportdefault{methods:{@confirm()@loading()asyncdeleteData(){awaitdelete()}}}本节定义的装饰器已经应用于本项目https://github。com/snowzijun/vue-vant-base,这是一个基于Vant开发的开箱即用的移动端框架。你只需要fork它,无需任何配置就可以直接开发你的业务。欢迎使用。喜欢的话请给个star吧。我是子君,今天写这么多。本文首发于【前端玩转】,是一篇关注前端技术和前端面试的公众号。关注后马上拉你进前端交流群。一起来Chat前端,欢迎关注。结语不要耗尽你的灵感和想象力;不要成为模型的奴隶。-文森特-梵高