你不知道但在Vue中非常实用的黑科技
时间:2023-04-02 12:01:15
HTML
最近几个月一直致力于iView的开源工作,完成了大大小小30多个UI组件,积累了很多经验Vue组件开发。还有很多有花样和黑科技的组件。其中一些功能在Vue文档中提到但很容易被忽略,还有一些没有写在文档中。今天就来说说Vue组件的进阶玩法。本文所写的大部分内容都用在了iView项目中。大家可以关注一下,结合源码研究其中的奥秘。项目地址:https://github.com/iview/iview目录递归组件自定义组件使用v-model使用$compile()在指定上下文中手动编译组件内联模板inline-template隐式创建Vue实例递归组件recursive组件是文档中介绍,只要为组件指定一个name字段,就可以在组件中递归调用自己,例如:variview=Vue.extend({name:'iview',template:'
'+//递归调用自己''+'
'})这种用法在业务中不常见,在iView的级联选择组件中使用了这个特性(https://github.com/iview/iview/tree/master/src/components/cascader)如下图所示:图中每一列都是一个组件(caspanel.vue),一开始我想到的是用v-for来渲染list,但是后来发现可扩展性极低,而且随着功能的丰富,实现难度大,处理逻辑多,所以改写成递归组件:
propsprops有很多,可以忽略,但关键的两个是data和sublist,即当前列数据和子集数据,因为事先不知道他们有多少下属,所以只需要将下属数据传递给组件本身即可。如果为空,则递归结束。Vue的设计真的很精妙。注意:Vue1.x和2.x都支持该方法。自定义组件使用v-model我们知道v-model是用来对表单元素进行双向绑定的,比如:{{data}}此时数据双向绑定,输入的内容会真实显示在页面上时间。在Vue1.x中,自定义组件可以使用props的.sync双向绑定,例如:在Vue2.x中,可以直接在自定义组件上使用v-model,例如:在组件my-component中,可以通过this.$emit('input')的数据的值已更改。虽然这在Vue1.x中不能使用,但是如果你的组件模板外层是input、select、textarea等支持绑定v-model特性的元素,也是可以使用的。比如my-component的代码是:也可以使用上面2.x的写法。使用$compile()在指定上下文中手动编译组件。注意:该方法是在Vue1.x中介绍使用。官方文档没有对这种方法给出任何说明。不要过分依赖这种方法。使用$compile()方法,可以在任意指定上下文(Vue实例)手动编译组件,iView新发布的表格组件Table中使用:https://github.com/iview/iview/tree/master/src/components/table/cell.vue由于table的column配置是通过一个Object传给props的,所以不能像slot一样用Vue代码自动编译这部分,因为传入的都是字符串,比如:{render(row){return`${row.name}`}}render函数最后返回一个字符串,里面包含一个自定义组件i-button,如果直接使用{{{}}}表示i-button不会被编译,所以为了支持在cell中渲染自定义组件,使用了$compile()方法。例如,我们在组件的父级别编译://代码片段consttemplate=this.render(this.row);//通过上面的渲染函数获取字符串constdiv=document.createElement('div');div.innerHTML=template;this.$parent.$compile(div);//在父上下文中编译组件this.$el.appendChild(cell);//将编译后的html插入到当前组件中,以便编译i-button。在某些时候使用$compile()确实可以带来好处,但是也有很多值得思考的问题:这种编译容易混淆作用域,所以你需要知道它是在哪个Vue实例上编译的;手动编译后,还需要在适当的时候使用$destroy()手动销毁;有时候容易重复编译,所以记得保存当前编译实例的id,可以通过Vue组件的_uid唯一标识(每个Vue实例都会有一个自增id,可以通过this._uid获取)另外,Vue1.x文档中还提到了另外一个$mount()方法,也可以实现类似的效果。在Vue2.x的文档中,有Vue.compile()方法,用于在render函数中编译模板字符串,读者可以一起看看。内联模板inline-template内联模板并不是什么新鲜事,在文档中也有说明,只是平时很少用到,所以很容易被忽略。简单的解释就是用组件的slot作为这个组件的模板,更加灵活:{{data}}因为使用了inline-template内联模板,子组件不需要声明模板,此时它的模板直接来自slot{{data}},而这个数据所在的上下文属于子组件,而不是父组件,所以使用内联模板最简单由此产生的误解是混淆范围。Vue实例的隐式创建在webpack中,我们都是采用.vue单文件模式进行开发,每个文件都是一个组件,通过components:{}在需要的地方使用组件。比如我们需要一个提示框组件,在parent中可能会这样写:Thisisthetitleoftheprompt这样写是没有问题的,但是从使用的角度来说,我们其实并不期望这样使用,而是原来的window.alert('This是提示标题')这样使用起来比较灵活。这时候很多人可能会用原生的JS拼字符串写一个函数,这是没有问题的。但是,如果你的提示框组件比较复杂,并且在多处复用,这个方法还是不太好。友好,并不能体现Vue的价值。iView在开发全局提示组件(Message)、通知提醒组件(Notice)、对话框组件(Modal)时,内部使用Vue渲染,但是JS隐式创建了这些实例,这样我们就可以像Message.info('title')是这样使用的,但它仍然由Vue内部管理。相关代码地址:https://github.com/iview/iview/tree/master/src/components/base/notification下面看一下具体实现:上图是最终的渲染图,这部分.vue代码比较简单,相信大家都能写出这样的组件,所以先说创建实例的部分,先看核心代码:importNotificationfrom'./notification.vue';从“vue”导入Vue;从'导入{camelcaseToHyphen}。./../../utils/assist';Notification.newInstance=属性=>{const_props=属性||{};让道具='';Object.keys(_props).forEach(prop=>{props+=':'+camelcaseToHyphen(prop)+'='+prop;});constdiv=document.createElement('div');div.innerHTML=``;文档.body.appendChild(div);constnotification=newVue({el:div,data:_props,components:{Notification}}).$children[0];返回{通知(noticeProps){notification.add(noticeProps);},删除(键){notification.close(键);},组件:notification,destroy(){document.body.removeChild(div);}}};导出默认通知;与上面介绍的$compile()不同的是,这个方法是直接在全局(body)中使用newVue创建一个Vue实例,我们只需要在入口处暴露几个API:importNotificationfrom'../base/notification';constprefixCls='ivu-message';consticonPrefixCls='ivu-icon';constprefixKey='ivu_message_key_';letdefaultDuration=1.5;lettop;letmessageInstance;letkey=1;consticonTypes={'info':'information-circled','success':'checkmark-circled','warning':'android-alert','error':'close-circled','loading':'load-c'};functiongetMessageInstance(){messageInstance=messageInstance||Notification.newInstance({prefixCls:prefixCls,style:{top:`${top}px`}});returnmessageInstance;}functionnotice(content,duration=defaultDuration,type,onClose){if(!onClose){onClose=function(){}}consticonType=iconTypes[类型];//如果正在加载constloadCls=type==='loading'?'ivu-load-loop':'';让实例=getMessageInstance();instance.notice({key:`${prefixKey}${key}`,duration:duration,style:{},transitionName:'move-up',content:`${content} `,onClose:onClose});//用于手动删除return(function(){lettarget=key++;returnfunction(){instance.remove(`${prefixKey}${target}`);}})();}exportdefault{info(内容,持续时间,onClose){返回通知(内容,持续时间,“信息”,onClose);},success(content,duration,onClose){returnnotice(content,duration,'success',on关闭);},warning(content,duration,onClose){returnnotice(content,duration,'warning',onClose);},error(content,duration,onClose){returnnotice(content,duration,'error',onClose);},loading(content,duration,onClose){returnnotice(content,duration,'loading',onClose);},config(options){if(options.top){top=options.top;}if(options.duration){defaultDuration=options.duration;}},destroy(){让实例=getMessageInstance();消息实例=空;instance.destroy();被调用,但我们也可以在Vue上扩展它:Vue.prototype.$Message=Message;这样我们就可以直接使用this.$Message.info()调用而不用导入MessagePostscriptVue组件开发有很多有趣的技巧,用得好会减少很多不必要的逻辑,用得不好的话,会弄巧成拙。当开发一个比较复杂的组件时,需要先研究设计技术方案,然后再进行编码。iView还有很多开发技巧和有趣的代码。以后有时间再继续讨论吧。最近发布的版本有重大更新。希望大家多多关注和推广iView?:https://github.com/iview/iview