当前位置: 首页 > Web前端 > vue.js

为什么我推荐使用JSX开发Vue3

时间:2023-04-01 11:07:54 vue.js

一直以来,Vue官方都以简单易用作为宣传。这确实为Vue带来了非常大量的用户,尤其是在国内最追求开发效率,往往不太关心工程代码质量的中小企业中,Vue的份额增长迅速。但作为开发者本身,我们必须认清一个关键点。易用性不应在技术选择中占据很大份额,但可维护性才是。免得有些同学真的不看官方文档,我提一下,SFC是写Vue组件时写的.vue文件。这个文件是一个SFC,全称是SingleFileComponent,也就是单文件组件。在开始谈我个人的看法之前,先看几个事实:首先,Vue3的定义是原生支持JSX的,Vue3源码中有jsx.d.ts,方便JSX的使用。不知道同学们看到这里会怎么想,我的第一反应是:社区对JSX的需求不小,所以我倒推一下Vue3官方对JSX的支持。二是:AntDesign的vue3版本基本都是用JSX开发的,而Vue3官方的babel-jsx插件一开始是阿里人维护的,虽然一直不喜欢阿里系的KPI提升技术,以及现在对JSX语法的支持不是很符合我的预期,但至少使用JSX开发是一个更好的选择,我还是认可AntDesign团队的。OK,说到这里,主要是先呈现一些事实作为依据,让一些同学不用拿什么:啊,这都是你的幻想,你太自以为是了,不管你怎么样想一想,我们Vue用SFC来开发这些视图来批评我是没有用的。首先,我会从客观的角度分析为什么,至少我能说出优缺点的原因。OK,前言就说到这里了,接下来就给大家分析一下为什么要选择JSX来开发Vue。TypeScript支持在第一点上实际上是一个杀手。对于想使用TypeScript开发Vue3应用的同学来说,这简直是SFC无法攻克的世界难题。一句话:TypeScript原生支持JSX语法,TS能正式支持SFC模板语法基本无望。毫无疑问,TS在前端社区中的地位越来越重要,但是未来任何对代码质量有一定要求的前端团队都应该选择使用TS进行开发。而且现在基本上在npm上都能看到package,也能找到对应的ts定义。现在使用TS的开发成本只是你会不会TS语法。在这种情况下,是否支持TS就是开发模式。未来走不远的一个重要原因。目前SFC只能让TS通过shim导入.vue文件,但是所有SFC组件的定义都是一样的:declaremodule'*.vue'{import{DefineComponent}from'vue'constcomponent:DefineComponent<{},{},{},any>exportdefaultcomponent}也就是说你导入的SFC组件,TS并不知道这个组件应该接收什么Props。所以你不能享受这些TS的好处:开发时自动提示,编译时TS校验,让你尽早发现问题编译组件生成你的组件定义(对类库开发尤其重要)当然你会这么说既然Vue能正式开发处SFC的语法自然会支持这些特性。我说这当然可以,但是难度很大,需要多方面的支持,甚至可能需要TS官方团队愿意协助,但是我想不出TS官方有什么理由支持SFC,因为这只是Vue自己创造的一种方言,并没有用在其他场景,TS是面向整个社区的,我觉得他们不会考虑主动支持SFC。那么有同学要问了,JSX不也是非原生JS语法吗,他怎么让TS官方支持,FB和微软之间有PY交易吗?这就涉及到第二点,JSX和静态模板在灵活性上的区别。JSX实际上不是方言。很多人都犯了错误。他们认为SFC的模板语法和JSX是一样的。它们都是别人发明的语法,而不是JS的原生语法。确实如此,但也有一些差异,主要体现在对JSX的认知上。一句话:JSX并没有扩展JS的语法,它只是缩短了JS的写法!本质是JS的语法糖就像es6加的语法糖一样,比如consta=1constb=2constobj={a,b}//其实等价于constobj={a:a,b:b}这种写法并没有扩展JS的能力,只是简单的让写起来更简单,JSX也是如此。JSX实际上是一种方法调用。它和JS是一一对应的。我们来看一个例子:constelement=HelloWorld

这里编译后的JSX语法实际上是:constelement=createElement('div',{id:'root'},'HelloWorld')而JSX也就那样,没有更多的内容,所以JSX只是我们编写嵌套函数调用的语法糖,而它本身并没有扩展任何其他东西。但证监会不同。SFC不仅定义了语法,还定义了文件。SFC的具体定义是单文件组件,它本身就是以文件为单位的,所以它的约束要大得多。必须有固定的文件结构才能使用SFC,这有很多限制:a文件只能写一个组件节点片段只能写在模板中,非常不灵活的变量绑定只能获取this上的内容,不能使用globalvariables(很多时候我们要先给这个挂载全局变量)我们说实话,一个文件只能写一个组件,很不方便。很多时候我们在写一个页面的时候,往往需要把一些小的节点碎片拆分成小的组件,以便复用(如果你现在没有这个习惯,可能是因为SFC的限制让你习惯了全部写在一个文件里)。React生态系统中丰富的css-in-js解决方案就是一个很好的例子。我们可以通过:constStyledButton=styled('button',{color:'red',})如果我们需要在这个页面使用特定样式的按钮,很常见的是通过这种方式封装在页面文件中.因为这个组件不需要拆分出来,所以不是可复用的组件,需要重新导入一次。Vue生态中对于css-in-js基本没有成熟的解决方案。其实,这种局限性也是密切相关的。我们再举一个例子。比如我们封装了一个Input组件。我们希望同时导出Password组件和Textarea组件,方便用户根据实际需要使用。这两个组件本身内部使用了Input组件,但是自定义了一些props:constInput={...}exportdefaultInputexportconstTextarea=(props)=>exportconstPassword=(props)=>在JSX中可以很简单的实现,但是如果使用SFC,可能要强行拆分成三个文件。另外,为了方便,你可能还需要添加一个index.js来导出这三个组件,你能想象这要多做多少工作吗。节点分片只能写在模板中,非常不灵活。不知道有多少同学看过Vue的模板编译后的代码。根据我的经验,它可能不会超过50%(乐观估计)。推荐如果同学们还不理解,可以尝试一下。为什么要看这个?因为你看完之后会发现你在模板里面写的类HTML的内容和HTML完全没有关系,它们也会被编译成类似JSX编译的结果。{render(h){returnh('div',{on:{},props:{}},h('span'))}}类似于这个结果,h函数调用的结果是一个VNode,是Vue中节点的基本单元。那么既然这些单位是一个对象,那么当然可以将它们作为参数传递。也就是说,理论上它们可以通过props将节点作为参数传递给其他组件。这种方式在React中很常见,叫做renderProps,而且非常灵活:constComp=()=>}footer={}/>但由于受限于SFC模板,我们很难在SFC中的props上写节点:这是不行的,因为SFC定义:headerbindingaccept只能是js表达式,而显然不是。Vue发明了插槽的概念,因为无法通过props传递。虽然口口声声说Vue简单,但实际上ScopedSlots一度成为新手理解Vue的噩梦。.让我们看一个ScopedSlots的例子:这里的ctx是Comp中的一个属性,通过这种方式传入,方便我们在当前组件中调用父组件中的属性。这理解起来简直是一场噩梦,但是如果你使用JSX来实现类似的功能,那就很简单了:
{name}
}/>我们只要传递一个名为作用域为一个Function,这个函数接受一个name属性,这个函数将在Comp中调用并传入name。简单的说,我们传入的就是一个构建节点分片的函数,就这么简单。这是因为SFC模板的限制,导致灵活性不够。Vue需要创建概念和关键字来弥补这些能力的不足,而创建的概念自然会引入学习成本。所以其实我一直不认同Vue比React好学的说法。如果你真的认真研究了所有的用法,总是尝试以最合理的方式实现功能,那么Vue绝对不会比React简单。变量绑定只能获取this上的内容,不能使用全局变量。这体现在两个方面。一是如果我们全局定义的一些固定数据要在组件中使用,就必须通过this挂载到组件上。比如我们缓存了一条城市数据,基本不会变化,所以没必要挂载到组件上让它响应。但这在SFC中是做不到的,因为模板的执行上下文是在编译时绑定的。你在模板中访问的变量会在编译时自动绑定到this上,因为模板需要编译,string这个概念本身没有作用域。而这在JSX中不再存在:constcities=[]constComp=()=>{returncities.map(c=>
{c}
)}另一个方面是在组件中使用,在SFC中,组件必须提前注册,因为我们在模板中写的只能是字符串,不能是具体的组件变量。那么模板中的组件和真正的组件对象只能通过字符串匹配来绑定。这带来了以下问题:注册组件这一步增加了代码量,通过字符串名称注册自然会有冲突的可能。模板解析组件支持不同的样式,比如,容易导致样式不同的问题在JSX中没有这些问题,因为JSX直接使用组件引用作为参数:constComp={...}constApp=()=>需要通过指令来扩展能力从上面可以看出,除了SFC本身的问题,Vue使用字符串模板也会带来很多的灵活性问题。最直接的证据就是Vue使用指令来扩展功能(当然这不是Vue发明的,旧的模板引擎也有类似的问题)。为什么该指令是最后的手段?因为静态模板缺乏处理逻辑的能力。我们以列表循环为例。在JS中,我们可以很方便的通过map函数来创建一个列表:constlist=arr.map(name=>{name})而由于JSX本身就是一个函数调用,所以上面的代码结合JSX也很自然:constApp=()=>(
{arr.map(name=>({name}))}
)上面的例子对应JS如下:constApp=()=>createElement('div',{},[
,arr.map(name=>createElement('span',{key:name},name)),])这还是因为JSX只是JS的语法糖,凡是JS能实现的,JSX都能实现。SFC模板是根据字符串编译的,字符串本身就是一个字符串。我们不能直接在模板中写map到loop节点(当然可以写在可以接收表达式的地方,比如v-on中)。所以我们不能循环节点,如果我们需要这样的函数来渲染列表怎么办?就是发明一个flag,告诉编译器这里需要循环,在Vue中的体现就是v-for指令。同学们可能会问,既然Vue可以实现v-for,为什么不直接实现表达式循环列表呢?他当然也可以实现,但他绝对不会选择这种方式,因为成本太高了。如果他要这样做,就相当于实现了一个JS引擎,但其实很多内容都是不必要的,一个v-for其实可以适用于大部分情况。但是有了v-for,就需要v-if了,后面还会需要各种其他的能力。这就是方言产生和发展的过程。当然,指令不仅仅是JS表达式的替代品,它还增加了一些其他的能力,比如可以让我们更方便的访问DOM节点,但是我们使用框架的原因并不是为了能够阻塞DOM操作~综上所述,以上是我对使用JSX还是SFC进行开发的分析。其实SFC的问题就是没有拥抱JS。它的语法是自己发明的。它需要一个由JS实现的编译器。让它运行在JS环境下,本质上是一个发明。我们不能否认发明确实有优点,但也不能光看而忽略问题。如果不拥抱JS,就很难完全复用JS社区,但是JS社区一直在蓬勃发展,好用的工具层出不穷,而SFC想利用JS社区的这些工具来实现另一个副本本身。我们可以数一数SFC做了哪些兼容vue-loader的。对于webpackeslint-plugin-vue,对于eslintrollup-plugin-vue,对于rollupvue-jest,对于jestVetur代码提示,基本常用的工具我们需要等待Vue社区或者官方开发的插件才能运行。并且由于JSX有babel和typescript的官方支持,基本上所有新的JS生态工具都原生支持。在Vue3准备发展的现阶段,我们还是希望Vue社区能够朝着更好、更规范的方向发展。其实如果我们直接使用JSX开发Vue3,我们会发现很多情况下不需要使用emit和attrs。这些概念,即使Vue3的JSX插件支持,我们甚至可以放弃slots。但是由于Vue3必须考虑与Vue2的兼容性,所以潜力不错的Vue3似乎总是害羞,不得不说是一个遗憾。最后,我相信Vue3的潜力,我会一直活跃在Vue3社区。想要获取更多优质的Vue3学习资料,可以关注BestVue3,更多优质内容正在制作中!