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

携程机票前端Svelte制作实践

时间:2023-03-23 11:01:05 科技观察

作者|冯栓,携程高级前端开发工程师,专注于性能优化、低代码、svelte等领域。一、技术研究近年来,前端框架层出不穷。近两年,前端圈出了一个新宠儿:Svelte。作者是RichHarris,Ractive、Rollup和Buble的作者,前端界的“轮子哥”。通过静态编译减少框架运行时的代码量。编译Svelte组件后,所有必需的运行时代码都包含在其中。除了组件本身,不需要引入所谓的框架运行时!在Github上拥有超过5w颗星!最新的StateofJS2021和StackOverflowSurvey2021排名也在一定程度上反映了它的受欢迎程度。之前的知识里,你是怎么看待svelte这个前端框架的?题下,Vue的作者游雨溪也给了很高的评价:去它的官网看看:官网明确展示了三大特点:少写代码无虚拟DOMT真正的reactive1.1写lesscode顾名思义,就是在Svelte中用最少的代码实现同样的功能。这将在下面的示例中显示。1.2NovirtualDOMSvelte的实现不使用虚拟DOM。要知道Vue和React的实现都是用的虚拟DOM,虚拟DOM不是一直很高效吗?VirtualDOM不是总是高效的吗?其实,VirtualDOM高效是一种误解。VirtualDOM效率高的原因之一是它不直接操作原生DOM节点,因为这会消耗大量的性能。当组件状态改变时,它会使用一些diff算法计算出本次数据更新的真实视图变化,然后只改变需要改变的DOM节点。用过React的同学可能会发现React并没有想象中的那么高效,框架有时会做很多无用的工作,体现在很多组件会“无缘无故”地重新渲染。所谓重新渲染就是重新执行你定义的类Component的render方法,或者重新执行你的组件函数。组件重新渲染是因为VitualDOM的效率是基于diff算法,如果存在diff,则必须重新渲染组件才能知道组件的新状态和旧状态是否发生了变化,所以至于计算哪个DOM需要更新。正是因为框架本身很难避免无用渲染,所以React允许你使用一些API比如shouldComponentUpdate、PureComponent、useMemo来告诉框架哪些组件不需要重新渲染,但是这也引入了很多模板代码。那么如何解决VirtualDOM算法效率低下的问题呢?最有效的解决方案是不使用VirtualDOM!1.3真正的响应式第三点是真正的响应式。上面也提到了前端框架要解决的首要问题是:当数据发生变化时,相应的DOM节点会被更新。这是反应性的。我们先看看Vue和React是如何实现响应式的。ReactReactive通过useState定义倒计时变量,在useEffect中通过setInterval每秒递减一次,然后在视图中同步更新。这种实现背后的原理是什么?React开发者使用JSX语法编写代码,JSX会被编译成ReactElement,运行时会生成一个抽象的VirtualDOM。然后每次重新渲染render时,React都会重新对比前后两个VirtualDOM,如果不需要update则什么都不做;如果只是HTML属性发生变化,会通过调用节点的setAttribute方法反映到DOM节点上;如果DOMtype发生变化、key发生变化或在新的VirtualDOM中找不到,则会执行相应的删除/添加DOM操作。Vuereactive实现了与Vue相同的功能。Vue背后是如何实现响应式的呢?大致流程是在编译时收集依赖,基于Proxy(3.x),defineProperty(2.x)getters,setters来在数据变化时通知Watcher。Vue和React等响应式方法存在哪些问题?diff机制给runtime带来了负担。开发者需要优化性能useMemouseCallbackReact.memo...那么Svelte是如何实现响应式的呢?Sveltereactive作为一个框架,其实需要解决的是当数据发生变化时,相应的DOM节点会更新(reactive)的问题。VirtualDOM需要比较新旧组件的状态来达到这个目的,而一个更高效的方法其实就是当数据发生变化时,直接更新对应的DOM节点。这就是Svelte所采用的方法。Svelte会在代码编译时将每次状态变化转化为DOM节点对应的操作,从而在组件状态变化时快速高效地更新DOM节点。深入理解后,发现它采用了Compiler-as-framework的概念,将框架的概念放在编译时而不是运行时。当你写的应用代码用Webpack或Rollup等工具打包后,会直接转化成JavaScript对DOM节点的原生操作,这样bundle.js就不会包含框架的runtime。那么Svelte能减少多少包大小呢?以下是RealWorld项目的统计数据:从上图可以看出,Svelte的bundlesize是Vue的1/4,React的1/20!单从这个数据来看,Svelte的框架确实对bundlesize做了很大的优化。看到如此强大的数据支撑,不得不说真的动心了!2、项目落地为了验证Svelte在营销h5的可能性,我们选择了口罩机项目:上图为口罩机项目的设计稿。不难看出,核心逻辑并不是很复杂,这也是我们选择它作为Svelte尝试的原因。首先,项目的基础设施是基于svelte-webpack-starter创建的,它集成了TypeScript、SCSS、Babel和Webpack5。不过这个基础模板只提供了简单的支持,比如项目中用到的一些图片和字体,需要loader单独处理。启动项目,熟悉的helloworld:先看看这里的核心。当然,在开发环境中使用webpack有时候不得不说体验不是很好。每次都需要几秒钟。我们改用Vite,基本上秒启动:Vite的配置也比较简单:2.1组件结构的区别与React组件不同,Svelte代码更像是我们以前写HTML、CSS和JavaScript的时候(这个很类似于Vue)。所有JavaScript代码都位于Svelte文件顶部的标记中。然后是HTML代码,也可以在标签中写样式代码。组件中的样式代码只对当前组件有效。这意味着为组件中的

标签编写的样式不会影响其他组件中的
元素。2.2生命周期Svelte组件有很多生命周期。主要有onMount,onDestoy,beforeUpdate,afterUpdate。onMount的设计类似于useEffect的设计。如果返回一个函数,返回的函数会在组件析构后执行,同onDestoy:2.3初始状态接下来是初始状态的定义:我们发现代码中没有使用类似React的setState方法时更新变量,但直接分配变量。只需给一个变量赋值就可以触发视图的变化,这显然是数据响应式的,这正是Svelte真正响应式的体现。2.4条件判断项目中用到了很多条件判断。React使用JSX,因此可以直接使用JS中的条件控制语句,而模板需要单独设计条件控制语法。例如,v-if在Vue中使用。在Svelte中使用了{#ifconditions}、{:elseif}、{/if},属于Svelte对HTML的增强。上面的代码中有这么一行:$:buttonText=isTextShown?'Showless':'Showmore'buttonText依赖变量isTextShown,当依赖发生变化时触发操作,类似于Vue中的computed,这里Svelte使用$:关键字来声明计算变量。这是什么黑科技?这里使用Statements和declarations语法,冒号:前面可以是任何合法的变量字符。2.5数据双向绑定项目中有很多地方需要实现双向绑定。我们知道React是一种单向数据流,所以我们需要手动触发变量更新。Svelte和Vue都是双向数据流。Svelte使用bind关键字完成类似于v-model的双向绑定。2.6ListLoop项目也大量使用了listloop渲染。Svelte使用{#eachitemsasitem}{/each}实现列表循环渲染,其中item可以被解构和赋值,从而获取item中的值。不得不说,在传递父子属性的时候有点像ejs2.7。与React中的props不同,Svelte使用export关键字将变量声明标记为属性。export不是传统的ES6export,而是一种语法糖。注意只有exportlet是声明属性2.8跨组件通信(状态管理)既然说到父子组件通信,就不得不提到跨组件通信,或者说状态管理。这个一直是前端框架比较关心的部分。Svelte框架无需安装单独的状态管理库即可实现商店本身。可以定义一个writablestore,然后在不同的组件之间进行读取和更新:每个writablestore其实就是一个对象,可以在需要用到这个值的组件中订阅他的变化,然后更新到自己的state中成分。在另一个组件中,可以调用set和update来更新这个状态的值。2.9路由Svelte目前没有提供官方的路由组件,但是可以在社区找到:svelte-routingsvelte-spa-routersvelte-routing和react-router-dom的使用方式类似:而svelte-spa-router更像vue-router一点:2.10的UI项目中也使用了组件库。通常react项目一般使用NFESUI,但毕竟是react组件,在Svelte中是不适用的。我们试图在社区中找到合适的SvelteUI库,查看了SvelteMaterialUI、CarbonComponentsSvelte等,但都不能完全满足我们的需求,只好自己重写了(只用了几个组件,重写成本不大)。2.11单元测试@testing-library/svelte用于单元测试:基本用法与React非常相似。业务代码迁移完成后,接下来就是对原有的功能用例进行逐一验证。为了验证单独使用Svelte进行开发的效果,我们没有进行其他优化。我们向生产线发布了一个仅包含Svelte的代码版本。让我们看看bundlesize(gzip之前)和lighthousescoring:另外,我们按照lighthouse给出的改进建议,对Performance、Accessibility和SEO做了进一步的优化和改进:Performance的改进主要是由于支持webp的图片格式和部分资源的延迟加载。Accessibility和SEO的提升主要是meta标签的调整。3.实践总结通过这次技术改造,我们对Svelte有了一些新的认识。整体而言,Svelte在三大前端框架之后进行了创新,以全新的思路实现了响应式。因为它的起步时间不是很长,国内使用的程度还比较小,目前生态还不够完善。但这并不能掩盖它的优点:够“轻”。Svelte非常适合活动页面,因为活动页面一般不会有很复杂的交互,主要是渲染和事件绑定。正如文章开头所言,一个简单的活动页面要用React这么重的框架,多少有些憋屈。所以对于一些营销团队来说,如果你想在包大小上有很大的突破,Svelte绝对可以成为你的选择。另外,社区现在对Svelte有很好的用处,就是用它来制作WebComponents。好处也很明显:使用框架开发,更容易维护框架无关的依赖,并且可以跨框架使用。体积小,所以对于那些想要实现跨框架组件复用的团队来说,使用Svelte来做WebComponent也是一个不错的选择。