携程机票前端Svelte生产实践

开发 前端
整体来说,Svelte 继前端三大框架之后推陈出新,以一种新的思路实现了响应式。因其起步时间不算很长,国内使用程度仍然偏少,目前来说其生态还不够完备。

​作者 | shuan feng,携程高级前端开发工程师,关注性能优化、低代码、svelte等领域。

一、技术调研

最近几年,前端框架层出不穷。近两年,前端圈又出了一个新宠:Svelte​。作者是 Rich Harris​,也就是 Ractive​, Rollup​ 和 Buble的作者,前端界的“轮子哥”。

通过静态编译减少框架运行时的代码量。一个 Svelte 组件编译之后,所有需要的运行时代码都包含在里面了,除了引入这个组件本身,你不需要再额外引入一个所谓的框架运行时!

在Github上拥有 5w 多的 star!

在最新的State of JS 2021和Stack Overflow Survey 2021的排名情况中,也一定程度上反映了它的火热程度。

在早前知乎的如何看待 svelte 这个前端框架?问题下面,Vue的作者尤雨溪也对其做出了极高的评价:

去它的官网看一下:

官网上清楚的表明了三大特性:

  • Write less code
  • No virtual DOM
  • Truly reactive

1.1 Write less code

顾名思义,是指实现相同的功能,Svelte的代码最少。这一点会在后面的示例中有所体现。

1.2 No virtual DOM

Svelte​的实现没有利用虚拟DOM​,要知道Vue和React​的实现都是利用了虚拟DOM​的,而且虚拟DOM不是一直都很高效的吗?

Virtual DOM 不是一直都很高效的吗?

其实 Virtual DOM​高效是一个误解。说 Virtual DOM​ 高效的一个理由就是它不会直接操作原生的 DOM​ 节点,因为这个很消耗性能。当组件状态变化时,它会通过某些 diff​ 算法去计算出本次数据更新真实的视图变化,然后只改变需要改变​的 DOM 节点。

用过 React​ 的同学可能都会体会到 React​ 并没有想象中那么高效,框架有时候会做很多无用功,这体现在很多组件会被“无缘无故”进行重渲染(re-render​)。所谓的 re-render​ 是你定义的 class Component​ 的 render 方法被重新执行,或者你的组件函数被重新执行。

组件被重渲染是因为 Vitual DOM​ 的高效是建立在 diff​ 算法上的,而要有 diff​ 一定要将组件重渲染才能知道组件的新状态和旧状态有没有发生改变,从而才能计算出哪些 DOM 需要被更新。

正是因为框架本身很难避免无用的渲染,React​ 才允许你使用一些诸如 shouldComponentUpdate,PureComponent​ 和 useMemo​ 的 API 去告诉框架哪些组件不需要被重渲染,可是这也就引入了很多模板代码。

那么如何解决 Vitual DOM​ 算法低效的问题呢?最有效的解决方案就是不用 Virtual DOM!

1.3 Truly reactive

第三点真正的响应式​,上面也提到了前端框架要解决的首要问题就是:当数据发生改变的时候相应的 DOM​ 节点会被更新,这个就是reactive。

我们先来看下Vue和React分别是如何实现响应式的。

React reactive

通过useState​定义countdown​变量,在useEffect​中通过setInterval使其每秒减一,然后在视图同步更新。这背后实现的原理是什么呢?

React​ 开发者使用 JSX​ 语法来编写代码,JSX​ 会被编译成 ReactElement​,运行时生成抽象的 Virtual DOM。

然后在每次重新 render​ 时,React​ 会重新对比前后两次 Virtual DOM​,如果不需要更新则不作任何处理;如果只是 HTML​ 属性变更,那反映到 DOM​ 节点上就是调用该节点的 setAttribute​ 方法;如果是 DOM​ 类型变更、key​ 变了或者是在新的 Virtual DOM​ 中找不到,则会执行相应的删除/新增 DOM 操作。

Vue reactive

用Vue​实现同样的功能。Vue背后又是如何实现响应式的呢?

大致过程是编译过程中收集依赖,基于 Proxy​(3.x) ,defineProperty​(2.x) 的 getter,setter​ 实现在数据变更时通知 Watcher。

像Vue和React这种实现响应式的方式会带来什么问题呢?

  1. diff 机制为 runtime 带来负担
  2. 开发者需自行优化性能
  • useMemo
  • useCallback
  • React.memo
  •  ...

那么Svelte又是如何实现响应式的呢?

Svelte reactive

其实作为一个框架要解决的问题是当数据发生改变的时候相应的 DOM​ 节点会被更新(reactive​),Virtual DOM​ 需要比较新老组件的状态才能达到这个目的,而更加高效的办法其实是数据变化的时候直接更新对应的 DOM 节点。

这就是Svelte​采用的办法。Svelte​会在代码编译的时候将每一个状态的改变转换为对应DOM​节点的操作,从而在组件状态变化的时候快速高效地对DOM节点进行更新。

深入了解后,发现它是采用了 Compiler-as-framework​ 的理念,将框架的概念放在编译时而不是运行时。你编写的应用代码在用诸如 Webpack​ 或 Rollup​ 等工具打包的时候会被直接转换为 JavaScript​ 对 DOM​ 节点的原生操作,从而让 bundle.js​ 不包含框架的 runtime。

那么 Svelte​ 到底可以将 bundle size​ 减少多少呢?以下是 RealWorld​ 这个项目的统计:

由上面的图表可以看出实现相同功能的应用,Svelte的bundle size​大小是Vue的1/4​,是React的1/20​!单纯从这个数据来看,Svelte​这个框架对bundle size的优化真的很大。

看到这么强有力的数据支撑,不得不说真的很动心了!

二、项目落地

为了验证Svelte​在营销 h5 落地的可能,我们选择了口罩机项目:

上图是口罩机项目的设计稿,不难看出,核心逻辑不是很复杂,这也是我们选用它作为Svelte尝试的原因。

首先项目的基础结构是基于svelte-webpack-starter创建的,集成了TypeScript、SCSS、Babel​以及Webpack5​。但这个基础模板都只进行了简单的支持,像项目中用到的一些图片、字体等需要单独使用loader去处理。

启动项目,熟悉的hello world:

这里看下核心的

当然开发环境使用​webpack​有时不得不说体验不太好,每次都要好几秒,我们就用Vite​来替代了,基本都是秒开:图片

Vite的配置也比较简单:

2.1 组件结构差异

和 React​ 组件不同的是,Svelte​ 的代码更像是以前我们在写 HTML、CSS​ 和 JavaScript​时一样(这点和Vue很像)。

所有的 JavaScript​ 代码都位于 Svelte​ 文件顶部的 <script></script>​ 标签当中。然后是 HTML​ 代码,你还可以在 <style></style>​ 标签中编写样式代码。组件中的样式代码只对当前组件有效。这意味着在组件中为 <div>​ 标签编写的样式不会影响到其他组件中的 <div> 元素。

2.2 生命周期

Svelte​ 组件的生命周期有不少,主要用到的还是 onMount​、 onDestoy、beforeUpdate、afterUpdate,onMount​ 的设计和 useEffect​ 的设计差不多,如果返回一个函数,返回的函数将会在组件销毁后执行,和 onDestoy 一样:

2.3 初始状态

接下来是对初始状态的定义:

我们发现代码在对变量更新的时候并没有使用类似React的setState​方法, 而是直接对变量进行了赋值操作。仅仅是对变量进行了赋值就可以引发视图的变化, 很显然是数据响应的, 这也正是Svelte的truly reactive的体现。

2.4 条件判断

项目中使用了很多的条件判断,React​由于使用了JSX​,所以可以直接使用JS​中的条件控制语句,而模板是需要单独设计条件控制语法的。比如Vue​中使用了v-if。

Svelte​中则是采用了{#if conditions}、{:else if}、{/if}​,属于Svelte​对于HTML的增强。

上面代码中有这么一行:

$: buttonText = isTextShown ? 'Show less' : 'Show more'

buttonText​依赖了变量isTextShown​,依赖项变更时触发运算,类似Vue​中的computed​,这里的Svelte​使用了$:​关键字来声明computed变量。

这又是什么黑科技呢?这里使用的是 Statements and declarations​ 语法,冒号:前可以是任意合法变量字符。

2.5 数据双向绑定

项目中有很多地方需要实现双向绑定​。我们知道React​是单向数据流,所以要手动去触发变量更新。而Svelte和Vue都是双向数据流。

Svelte​通过bind​关键字来完成类似v-model的双向绑定。

2.6 列表循环

项目中同样使用了很多列表循环渲染。Svelte​使用 {#each items as item}{/each}​ 来实现列表循环渲染,这里的item​可以通过解构赋值,拿到item里面的值。

不得不说有点像ejs

2.7 父子属性传递

父子属性传递时,不同于React​中的props,Svelte​ 使用 export​ 关键字将变量声明标记为属性,export​ 并不是传统 ES6 的那个导出,而是一种语法糖写法。

注意只有 export let 才是声明属性

2.8 跨组件通讯(状态管理)

既然提到了父子组件通讯,那就不得不提跨组件通讯​,或者是状态管理​。这也一直是前端框架中比较关注的部分,Svelte​ 框架中自己实现了 store​,无需安装单独的状态管理库。你可以定义一个 writable store, 然后在不同的组件之间进行读取和更新:

每个 writable store​ 其实是一个 object​, 在需要用到这个值的组件里可以 subscribe​ 他的变化,然后更新到自己组件里的状态。在另一个组件里可以调用 set和update 更新这个状态的值。

2.9 路由

Svelte 目前没有提供官方路由组件,不过可以在社区中找到:

  • svelte-routing
  • svelte-spa-router

svelte-routing和react-router-dom 的使用方式很像:

而svelte-spa-router​更像vue-router一点:

2.10 UI

项目中也用到了组件库,通常react​项目一般都会采用NFES UI​,但毕竟是react component​,在Svelte​中并不适用。我们尝试在社区中寻找合适的Svelte UI​库,查看了Svelte Material UI、Carbon Components Svelte等,但都不能完全满足我们的需求,只能自己去重写了(只用到了几个组件,重写成本不算很大)。

2.11 单元测试

单元测试用的是@testing-library/svelte:

基本用法和React是很类似的。

业务代码迁移完毕,接着就是对原有功能case的逐一验证。

为了验证单单使用Svelte​进行开发的效果,我们没有进行其他的优化,发布了一版只包含Svelte​的代码到产线,来看下bundle size​(未做gzip​前)和lighthouse​评分情况:

除此之外,我们遵循lighthouse​给出的改进建议,对Performance、Accessibility和SEO做了更进一步的优化改进:

Performance​的提升主要得益于图片格式支持webp​以及一些资源的延迟加载,Accessibility和SEO​的提升主要是对meta标签的调整。

三、实践总结

通过这次技改,我们对Svelte有了一些全新的认知。

整体来说,Svelte 继前端三大框架之后推陈出新,以一种新的思路实现了响应式。

因其起步时间不算很长,国内使用程度仍然偏少,目前来说其生态还不够完备。

但这不能掩盖其优势:足够“轻”。Svelte​非常适合用来做活动页,因为活动页一般没有很复杂的交互,以渲染和事件绑定为主。正如文章最开始说的,一个简单的活动页却要用React​那么重​的框架多少有点委屈自己。所以对于一些营销团队,想在bundle size​上有较大的突破的话,Svelte是绝对可以作为你的备选方案的。

另外现在社区对于Svelte​还有一个很好的用法是使用它去做Web Component,好处也很明显:

  • 使用框架开发,更容易维护
  • 无框架依赖,可实现跨框架使用
  • 体积小

所以对于想实现跨框架组件复用的团队,用Svelte​去做Web Component也是一个很好的选择。

责任编辑:未丽燕 来源: 携程技术
相关推荐

2022-05-13 09:27:55

Widget机票业务App

2020-12-04 14:32:33

AndroidJetpackKotlin

2023-08-25 09:51:21

前端开发

2023-05-12 10:14:38

APP开发

2017-04-11 15:11:52

ABtestABT变量法

2022-06-10 08:35:06

项目数据库携程机票

2023-11-13 11:27:58

携程可视化

2024-09-10 16:09:58

2022-08-06 08:27:41

Trace系统机票前台微服务架构

2017-01-10 16:04:02

容器MySQL实践

2022-06-27 09:36:29

携程度假GraphQL多端开发

2017-04-11 15:34:41

机票前台埋点

2023-09-05 07:40:37

PythonSDKAPI

2021-03-12 07:47:44

KubernetesRedis-clustRedis

2023-07-12 16:07:50

链路数据湖技术

2022-07-15 12:58:02

鸿蒙携程华为

2017-03-15 17:38:19

互联网

2023-02-08 16:34:05

数据库工具

2022-08-12 08:34:32

携程数据库上云

2022-07-15 09:20:17

性能优化方案
点赞
收藏

51CTO技术栈公众号