React, Vue和Angular差不多占据了Web开发的大部分江山,可是最近半年Svelte开始逐渐吸引越来越多人的眼球。这个Svelte框架到底有什么过人之处呢?本文将会为大家分析一下Svelte火起来的原因,并且通过使用Svelte去搭建一个简单的书店应用(bookshop)来帮助大家快速入门这门框架。
要想知道Svelte为什么会火,首先得看看React和Vue这些框架存在什么问题。
React和Vue都是基于runtime的框架。所谓基于runtime的框架就是框架本身的代码也会被打包到最终的bundle.js并被发送到用户浏览器。当用户在你的页面进行各种操作改变组件的状态时,框架的runtime会根据新的组件状态(state)计算(diff)出哪些DOM节点需要被更新,从而更新视图。那么这些runtime代码到底有多大呢,可以看一些社区的统计数据:
Name | Size |
---|---|
Ember 2.2.0 | 435K |
Ember 1.13.8 | 486K |
Angular 2 | 566K |
Angular 2 + Rx | 766K |
Angular 1.4.5 | 143K |
Vue 2.4.2 | 58.8K |
Inferno 1.2.2 | 48K |
Preact 7.2.0 | 16K |
React 0.14.5 + React DOM | 133K |
React 0.14.5 + React DOM + Redux | 139K |
React 16.2.0 + React DOM | 97.5K |
从上面的表格可以看出常用的框架中,最小的Vue都有58k,React更有97.5k。换句话说如果你使用了React作为开发的框架,即使你的业务代码很简单,你的首屏bundle size都要100k起步。当然100k不算很大,可是事物都是相对的,相对于大型的管理系统来说100k肯定不算什么,可是对于那些首屏加载时间敏感的应用(例如淘宝,京东主页),100k的bundle size在一些网络环境不好的情况或者手机端真的会影响用户体验。那么如何减少框架的runtime代码大小呢?要想减少runtime代码的最有效的方法就是压根不用runtime。其实回想一下Web开发的历史,很早之前在用Jquery和Bootstrap一把梭的时候,我们的代码不就是不包含runtime的吗?当数据变化时直接通过JavaScript去改变原生DOM节点,没有框架那一系列diff和调度(React Fiber)的过程。这时你可能会问,要减少bundle size真的要回到那个刀耕火种的时代吗?有没有那种既可以让我用接近React和Vue的语法编写代码,同时又不包含框架runtime的办法。这恰恰就是Svelte要做的东西,它采用了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的优化真的很大。
什么?Virtual DOM不是一直都很高效的吗?其实Virtual DOM高效是一个误解。说Virtual DOM高效的一个理由就是它不会直接操作原生的DOM节点,因为这个很消耗性能。当组件状态变化时它会通过某些diff算法去计算出本次数据更新真实的视图变化,然后只改变“需要改变”的DOM节点。用过React的人可能都会体会到React并没有想象中那么高效,框架有时候会做很多无用功,这体现在很多组件会被“无缘无故”进行重渲染(re-render)。注意这里说的re-render和对原生DOM进行操作是两码事!所谓的re-render是你定义的class Component的render方法被重新执行,或者你的组件函数被重新执行。组件被重渲染是因为Vitual DOM的高效是建立在diff算法上的,而要有diff一定要将组件重渲染才能知道组件的新状态和旧状态有没有发生改变,从而才能计算出哪些DOM需要被更新。你可能会说React Fiber不是出来了吗,这个应该不是问题了吧?其实Fiber这个架构解决的问题是不让组件的重渲染和reconcile的过程阻塞主线程的执行,组件重渲染的问题依然存在,而且据反馈,React Hooks出来后组件的重渲染更加频繁了。正是因为框架本身很难避免无用的渲染,React才允许你使用一些诸如shouldComponentUpdate,PureComponent和useMemo的API去告诉框架哪些组件不需要被重渲染,可是这也就引入了很多模板代码(boilerplate)。如果大家想了解更多关于Virtual DOM存在的问题,可以看一下virtual dom is pure overhead这篇文章。
那么如何解决Vitual DOM算法低效的问题呢?最有效的解决方案就是不用Virtual DOM!其实作为一个框架要解决的问题是当数据发生改变的时候相应的DOM节点会被更新(reactive),Virtual DOM需要比较新老组件的状态才能达到这个目的,而更加高效的办法其实是数据变化的时候直接更新对应的DOM节点:
if (changed.name) {
text.data = name;
}
这就是Svelte采用的办法。Svelte会在代码编译的时候将每一个状态的改变转换为对应DOM节点的操作,从而在组件状态变化的时候快速高效地对DOM节点进行更新。根据js framework benchmark的统计,Svelte在对一些大列表操作的时候性能比React和Vue都要好。
Svelte是由RollupJs的作者Rich Harris编写的编译型框架,没了解过RollupJs的同学可以去它官网了解一下,它其实是一个类似于Webpack的打包工具。Svelte这个框架具有以下特点:
Svelte这个框架与Vue和React之间最大的区别是它除了管理组件的状态和追踪他们的渲染,还有很多其他有用的功能。例如它原生支持CSS scope和CSS animation。如果你用React或者Vue是需要引入第三方库来实现同样的功能的,而第三方依赖的引入会给开发者增加学习和维护的成本。
接下来我们会从头开始搭建一个基于Svelte框架的简单书店应用bookshop,通过这个demo,希望大家可以理解Svelte的一些基本概念和掌握它的一些基本用法并能够使用Svelte去搭建更加复杂的应用。
Bookshop应用支持以下功能:
项目的源代码可以在我的github仓库找到。
首先在我们的本地开发环境新建一个项目文件夹:
mkdir svelte-bookshop
接着用svelte官方的脚手架去初始化我们的应用:
npx degit sveltejs/template svelte-bookshop
cd svelte-bookshop
yarn
yarn dev
degit这个命令会将github上面的项目文件直接拷贝到某个本地文件夹,这里使用到的svelte/tempalte模板项目的github地址是这个。以上命令成功运行后,访问http://localhost:5000你会看到如下界面:
界面很简单就是展示一个hello world,接着让我们看一下生成的项目目录结构:
生成的代码主要包含以下文件目录结构:
接着让我们具体看一下src文件夹底下的各个文件内容
<script>
export let name;
</script>
<main>
<h1>Hello {name}!</h1>
<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>
<style>
main {
text-align: center;
padding: 1em;
max-width: 240px;
margin: 0 auto;
}
h1 {
color: #ff3e00;
text-transform: uppercase;
font-size: 4em;
font-weight: 100;
}
@media (min-width: 640px) {
main {
max-width: none;
}
}
</style>
这个文件定义了一个叫做App的Svelte组件,这里要注意App.svelte文件内并没有定义组件的名称,组件的名称是由它的文件名确定的。Svelte组件的文件名都是以.svelte结尾的,一个组件文件通常会包含以下三部分内容: