前言vue3.0-beta版已经发布有一段时间了,正式版据说是年中发布(直播说是年中或年末,网上传言是六月)。他说自己学不会,但是老老实实的创建了一个vue3项目。当他兴致勃勃的介绍vue2插件的时候,眉头一皱,发现事情并没有那么简单。浏览器无情的抛出错误:UncaughtTypeError:Cannotsetproperty'$toast'ofundefined不是说兼容vue2的写法,插件不兼容。怎么了?抱怨过之后,还是要解决问题。研究插件的代码,它是这样实现的varToast={};Toast.install=function(Vue,options){Vue.prototype.$toast='HelloWorld';}module.exports=Toast;当vue2插件将Expose一个安装方法并通过全局方法Vue.use()使用该插件时。install方法的第一个参数是Vue构造函数,插件方法是在Vue构造函数的原型对象中添加的。通过newVue()实例化后,可以在组件中调用this.$toast来使用组件。(具体实现可以参考我之前的文章:Vue.js插件开发详解,以下也是基于这个插件demo进行对比修改)。但是vue3的install方法没有传入vue的构造函数,没有prototype对象,vue.prototype是undefined,所以报错。Vue3采用依赖注入,在插件内部使用provide和inject,并暴露一个组合函数供组件使用。插件实现基本框架下面先实现一个插件的基本框架。从'vue'导入{提供,注入};constToastSymbol=Symbol();//使用Symbol创建唯一标识,多个插件之间不会有冲突const_toast=()=>{}//插件导出函数的main方法provideToast(config){//暴露的方法,提供_toast方法给后代组件provide(ToastSymbol,_toast);}exportfunctionuseToast(){//后代组件可以从这个方法中得到toast方法consttoast=inject(ToastSymbol);如果(!toast){抛出新的错误('错误');}returntoast;}组件在provideToast方法中使用,提供了_toast方法,然后在根组件中调用provideToast方法,传入插件参数,子组件中就可以使用toast函数了。//app.vue根组件import{provideToast}from'vue2-toast';exportdefault{setup(){provideToast({width:'200px',//配置toast宽度duration:2000//配置toast连续显示时长});}};在useToast方法中返回toast方法,然后在所有子组件中调用useToast方法获取toast方法。//child.vue子组件import{useToast}from'vue2-toast';exportdefault{setup(){constToast=useToast();//所有子组件必须从useToast中获取toast方法Toast('HelloWorld');}};数据响应如果要控制toastDOM元素的显示和隐藏以及提示文字的变化,需要创建响应式数据。在vue3中,可以通过reactive方法创建响应式对象。conststate=reactive({show:true,//DOM元素是否显示text:''//提示文字});挂载DOM是为了在页面上显示一个toast,所以必然要创建一个DOM并挂载到页面上。在vue2中,通过Vue.extend创建一个子类,将子类生成的实例挂载到一个元素上,而在vue3中,则通过createApp方法实现。const_toast=text=>{状态。显示=真;状态。文字=文字;toastVM=createApp({setup(){return()=>h('div',{style:{display:state.show?'block':'none'}//显示设置showhidden},state.text);}});toastWrapper=document.createElement('div');toastWrapper.id='吐司';document.body.appendChild(toastWrapper);//在页面中添加一个节点来挂载toast元素toastVM.mount('#toast');};完整示例以上每一步都是这个插件的重点内容,接下来就是完善细节,比如用户配置插件的全局参数,设置吐司样式,时长,显示位置等,都是这里不详述。完整示例如下:import{provide,inject,reactive,createApp,h}from'vue';constToastSymbol=Symbol();constglobalConfig={type:'bottom',//toastpositionduration:2500,//durationwordWrap:false,//是否自动换行width:'auto'//width};conststate=reactive({show:false,//toast元素是否显示text:''});let[toastTimer,toastVM,toastWrapper]=[null,null,null];const_toast=text=>{state.show=true;state.text=文本;if(!toastVM){//如果toast实例存在则不重新创建toastVM=createApp({setup(){return()=>h('div',{//这里是根据配置类不同的样式:['lx-toast',`lx-toast-${globalConfig.type}`,globalConfig.wordWrap?'lx-word-wrap':''],style:{display:state.show?'block':'无',宽度:globalConfig.width}},state.text);}});}if(!toastWrapper){//如果节点不重新创建toastWrapper如果它存在=document.createElement('股利');toastWrapper.id='lx-toast';document.body.appendChild(toastWrapper);toastVM.mount('#lx-toast');}如果(toastTimer)clearTimeout(toastTimer);//计时器,在持续时间后隐藏toastTimer=setTimeout(()=>{state.show=false;clearTimeout(toastTimer);},globalConfig.duration);};exportfunctionprovideToast(config={}){for(constkey在配置中){globalConfig[key]=config[key];}provide(ToastSymbol,_toast);}导出函数useToast(){consttoast=inject(ToastSymbol);如果(!toast){抛出新错误('错误');}returntoast;}总结和vue2插件比较大的区别是每个需要使用toast的组件都需要引入useToast方法。看起来有点麻烦,其实这也是vue组合API的目的。为了使代码更具可读性和易懂性,显然这个方法来自这里。正如标题所示,这是早期采用者版本。vue3的正式版还没有发布。不敢问以后会不会有更多的插件形式,至少这个是比较稳定的。
