当前位置: 首页 > Web前端 > HTML5

Vue3的碎片化:喜悦-迷茫-失望

时间:2023-04-05 22:54:16 HTML5

作为Vue的死忠粉,React每天都在被折腾。没想到这次针对的是Vue3。本来对新版本的Vue3充满了期待,但体验之后,却大失所望。可惜Vue3保守!本文介绍了Vue3的基本使用,Vue3的设计理念,组合API的实现原理,以及个人的思考。可能会有不妥和狭隘的地方,欢迎大家批评指正。前言不懂Vue3的同学可以通过Vite工具快速搭建Vue3开发环境,参考《Vue3文档》接入开发。在《Vue3组合式API征求意见稿》中,我们可以一窥Vue3的设计思路和技术选择的判断过程。其中,《为什么不支持class?》和《和React-Hook的相比》的分析值得一看。下面提到的template的所有用法都参考了Vue文件的HTML模板的相关用法。Vue3实战体验为了了解Vue3的用法,通过下面Vue3的新特性实现定时效果,应该可以覆盖大部分应用场景:jsxTypeScriptprops结合API生命周期import{ref,defineComponent,onMounted,onBeforeUnmount}from"vue";exportinterfacePropsType{title:string;}exportdefaultdefineComponent({props:{title:String},setup:(props)=>{lettime=ref((newDate()).toLocaleString());让timerForDate:number;onMounted(()=>{timerForDate=window.setInterval(()=>{time.value=(newDate()).toLocaleString()},1e3);});onBeforeUnmount(()=>{if(timerForDate){clearInterval(timerForDate)}});让renderDate=()=>{return(

currenttime:{time.value}

)}return()=>(<>

{props.title}

{renderDate()})}})在写demo的过程中,逐渐了解到更多Vue3的新特性,以及新特性之间的界限而老版本的Vue2和Vue3也变得模糊了。这是好消息还是坏消息,以下是我个人的一些困惑。vue3的碎片化jsx复杂虽然vue3的jsx在vue2的基础上做了优化,但是还是有一些复杂的语法。具体语法参考官方文档:
A
,foo:()=>B}}/>对比React简单明了的jsx语法方面,Vue在jsx中保留了指令、装饰器、槽等复杂语法,大大提高了入门门槛,而这些语法只是为了解决模板的局限性,不需要在jsx中存在:指令(directive)是100%为模板,无一例外地引入slots&scopedslots(作用域插槽)slots完全实现了对标jsx的功能。scopeslot是vue2版本“中途”引入的功能。为了解决表格单元格数据渲染等场景,以往都是通过formatter方法来实现渲染逻辑。dom插入只能通过html字符串的拼接实现,cell无法实现。与页面数据相关联,所以引入了scopedslots的概念。模板相关的功能不能很好的连接到TypeScript,包括这里的slot。修饰符(Modifiers)因为模板中很难写出复杂的语句,所以需要将代码提取到JS代码中。修改器可以在这里降低成本。熟悉jsx的我们都知道jsx不存在上面的问题,也不需要上面的语法特性。如果非要访问这些特性,其实更好的解决方案是以更“函数式”的方式引入它们,比如更好的修饰符(Modifiers)的写法:import{throlle,prevent}from"@vue/modifiers"{})}/>{})}/>例如,使用jsx函数代替插槽:{return(
copyright.{data.title}
);}}/>函数式风格可以大大减少jsx的复杂语法,降低jsx的访问门槛,减少反复查阅文档的时间。我个人的建议是,如果业务方要引入Vue3,就应该彻底放弃template的使用,同时在jsx中,减少甚至禁用slots、modifiers等功能。slots是很难被禁用的,因为生态决定了slots等功能在业务端是不能被禁用的。我会提到#为什么Vue2不是问题,而Vue3却成了问题?#函数组件和状态组件的隔离Vue3明确区分了纯函数和状态组件。简单地说,Vue3中的纯函数不能有状态(stateless):import{ref}from"vue";exportdefault()=>{letcount=ref(0);constinc=()=>{count.value++;};return(
Purefunctionshavenostateandcannotbecounted:{count.value}
)}在上面的demo中,点击按钮会给出count+1,但是Vue3中的纯函数组件会重新渲染,Count的值永远为0。如果你想有状态(stateful),代码必须用defineComponent标记为状态组件:import{ref,defineComponent}from"vue";exportdefaultdefineComponent(()=>{letcount=ref(0);constinc=()=>{count.value++;};//注意!这里返回一个函数return()=>(<>

{props.msg}

有状态组件点击计数:{count.value})});相对于React的用法,Vue3的写法确实有些臃肿。我们可以先思考一下Vue3的两个设计点:1、为什么要用defineComponent来包裹?为了支持TypeScript的类型检查,defineComponent实际返回的是一个对象,上面的代码实现会通过setup字段返回。object是为了兼容更多的配置,比如inheritAttrs等区分无状态纯函数,兼容Vue组件注册机制。由于Decorator(装饰器)不能应用于纯函数,只能用函数包裹起来。2、为什么不能直接返回jsx??说白了,为什么setup函数类型一定要是()=>(()=>JSX)而不是()=>JSX?个人猜测,估计是Vue3用的比较坑爹的方案。当setup函数运行时:范围内的代码相当于初始化代码返回的函数(beforeCreate/Created)将被视为“render”函数。这样一来可以拆分初始化和渲染函数,避免重复执行初始化代码,二来可以不伤筋骨的连接到Vue的实现上。“断骨”是相对于React而言的。React的hook功能在低版本中是不可能通过运行时兼容代码来实现的。不过Vue3的方案与React相比,有一个小小的不同:通过返回闭包函数大大降低了技术难度。除了Vue3,我们甚至可以通过低版本的Vue2实现同样的用法和功能。下面我做了一个小demo。我在composition-api.js中写了一个简单的组合API函数。我在老版本的Vue2中实现了新版本的用法。可以进入codeSandbox查看效果://composition-api.jsletcurrentComponent;exportfunctionref(value){letcComponent=currentComponent;让refKey=cComponent.refKey++;cComponent.$set(cComponent.refData,refKey,value);返回{获取值(){返回cComponent.refData[refKey];},设置值(值){cComponent.refData[refKey]=value;}};}functionsetComp(com){currentComponent=com;}exportfunctiondefineComponent(setup){return{data(){return{refKey:0,refData:{}};},created(){setComp(this);this.renderFunc=setup();设置补偿(空);},render(h){返回this.renderFunc(h);}};}//app.jsimport{defineComponent,ref}from"./composition-api";exportdefaultdefineComponent(()=>{letcount=ref(0);constinc=()=>{count.value++;};returnh=>(

Vue2实现组合API+defineComponent

点我!计数:{count.value}
);});通过上面的介绍,我们可以充分理解“Vue3是这样设计的”的原因,但是在大版本中Vue3的“大而大胆”的创新变成“小步走”的场景下是不能接受的”,Vue3和React的对比就会很明显——把“纯函数”变成“状态组件”需要改多少代码?Vue3:添加defineComponent记得把()=>jsx改成()=>()=>jsx添加props根据props接口定义React,只需一步操作:引用useState。props的第二次声明Vue3接入TypeScript后,props需要两次定义:props接口用于导出组件的参数类型,业务场景会用到vue3-props定义。只有添加这个定义,组件才能接收父作用域传过来的props属性;同时为了让调试插件(Vue-devtool)显示组件的props定义;//1.支持业务方引入组件导出接口的参数类型PropsType{title:string;}exportdefaultdefineComponent({//2.必须在这里定义,props将为空props:{title:String,},setup:(props)=>{return()=>(

{props.title}

)}})第二次声明会增加代码维护成本,第二次定义也没有意义。这个问题已经在Vue官方仓库跟进:https://github.com/vuejs/vue-...解决办法?我个人的想法是在编译阶段解决:只写TypeScript接口,在编译阶段将接口编译成props定义,根据函数类型判断()=>()=>JSX,转换成纯函数进入一个组件://在开发过程中导出接口PropsType{title:string;}exportdefaultfunctionTitleComponent(props:PropsType){return()=>(

{props.title}

);};//编译后exportinterfacePropsType{title:string;}exportdefaultdefineComponent({name:"TitleCompnent",props:{title:String,},setup:(props)=>{return()=>(

{props.title

)}})这样开发时只需要统一维护一个props定义(即上面的接口),编译时自动生成Vue3的“props”定义流程和原有界面保留,兼顾两者。同样,vue3不支持的类也有可能成为可能:由于传递给泛型参数的接口只是一个类型注解,对于prop的代理行为,用户仍然需要为此提供其运行时声明。这个二级声明是多余的和笨拙的。为什么要放弃模板?为了支持模板功能,Vue需要添加很多特性,比如槽、指令、装饰器、道具定义、公共设置(inheritAttrs)等功能。即使模板有这些特性的加持,仍然无法与JSX相提并论。可以说Vue3对模板的保留得不偿失,1.造成了开发的局限;2.增加了JSX的复杂度。模板的局限性在于无法编写复杂的语句,必须抽取到方法或计算属性中再引用。TypeScript很难进行代码提取和集成。template和jsx冲突,即jsx不能只在同一个模板中渲染一个文件中重复的模板片段很难提取出来(不可能把所有的小片段都拆开,只能重复写).如果组件是模板,则无法通过props传递给jsx进行渲染,slot模板的使用肯定已经过时了。但令人担忧的是,早在Vue2时代,模板的实现就已经和jsx挂钩了:模板编译成jsx,转成虚拟dom(vdom)。游达认为template是一种比jsx更好的语法,业务场景应该用template来写,但是需要加入很多特性才能达到jsx的通用性。从Vue3的角度来看,这是一个很矛盾的点。template的用法确实是过时了,鸡肋了弃得更快,还不算鸡肋。Vue的困境Vue2时代的优势变成了Vue3的困境:向后兼容(progressive)运行时(runtime)覆盖所有功能。可能是以上两点,导致Vue3在CompromiseonCompositionAPI中,仍然坚持模板,JSX支持不到位。为什么在Vue2不是问题,到Vue3就成了问题?Vue2是大版本,大版本不应该引入破坏性的修改,所以一定要兼容模板、指令等用法。而在Vue2时代,我们“各有千秋”,你用template,我用jsx,互不影响。但是Vue3是新版本,其中一个主要目的就是接入TypeScript,但是对于老功能,妥协了太多东西,创新并不彻底。考虑到Vue3的生态问题,即使项目方坚持使用JSX,放弃模板相关功能,Vue3的开源生态也是不可控的。不管项目方怎么规范,开源组件还是要用的。接入生态必然涉及到插槽、指令等模板功能。毕竟jsx语法复杂、缺少props接口等问题是无法避免的。CompositionAPI是个好东西?组合API是未来吗?是好的设计吗?这对于Vue3来说是无关紧要的,因为Vue3已经放弃了class方案,组合API成为了Vue3接入TypeScript的唯一选择。按照React的尿性,React-Hook并不是什么高端的东西。至于好处,考虑到React的生命周期hook常年乱糟糟的,所以最终将其纳入了Hook的统一接口。从这里来看,还是有好处的。综上所述,虽然上面提到的Vue3有很多缺点,但是Vue的优点还是需要被认可的:清晰明了的生命周期也是React同行掀起的良好的数据惰性处理和数据监控,省了很多的工作量。》框架能优化的东西都在框架里,解决“Computed”的精彩案例,全靠代码实现的集合,非常优雅巧妙,值得大家一读。而且,这个设计包括export的数据更新,极大的避免了漏更新的问题。稳定严谨的迭代,Vue的双刃剑(渐进+向后兼容),相比隔壁React在小版本号引入破坏性更新的性追求终极,参考有大为什么放弃webpack的稳定生态,有大介入了大部分Vue生态的建设,理念相同,质量保证平衡功能和开发体验Vue的优势非常突出,希望在新版本之前Vue3正式上线,着重提炼优势特性,摒弃旧有功能,如果大踏步前进,Vue3还是值得期待的。最后,个人建议在Vue3版本中,无论是公共开源组件还是业务代码,都不要使用模板相关的功能(包括槽、指令、修饰符等),这样可以更好的对接jsx+打字稿。