最近发现 Svelte 超级火,去了官网上看了看,发现语法是从 Ractive 传承过来的和 Vue 非常像。看上去平平无奇,好像配不上它的热度。这几天读了一下 twiiter 和 相关的文章,发现其的亮点非常亮,可以说是提供了新的前端思路。

少写代码

只要写代码就会出错,任何多余的代码可能会造成新的错误,这个其实和 ProComponents 思路是相同的,但是 Svelte 是通过提升可读性来实现的。它使用了模板语法导致其看起来非常像 Vue, 下面的代码看起来会非常眼熟。

  1. <script>
  2. let a = 1;
  3. let b = 2;
  4. </script>
  5. <input type="number" bind:value={a}>
  6. <input type="number" bind:value={b}>
  7. <p>{a} + {b} = {a + b}</p>

同样的功能用 react 来实现

  1. import React, { useState } from 'react';
  2. export default () => {
  3. const [a, setA] = useState(1);
  4. const [b, setB] = useState(2);
  5. function handleChangeA(event) {
  6. setA(+event.target.value);
  7. }
  8. function handleChangeB(event) {
  9. setB(+event.target.value);
  10. }
  11. return (
  12. <div>
  13. <input type="number" value={a} onChange={handleChangeA}/>
  14. <input type="number" value={b} onChange={handleChangeB}/>
  15. <p>{a} + {b} = {a + b}</p>
  16. </div>
  17. );
  18. };

可以看到 Svelte 的确代码量会少很多,减少了很多的样板代码,尤其是 useState 和 事件的代码。这方面的确清爽好多。

没有虚拟 DOM

虚拟 DOM 通常是很快的,但是封装了一层总会损失性能,三大主流框架都使用了 虚拟 DOM , Svelte 的代码是直接操作 DOM, 从性能表现来看,非常非常快。尤其是没有了 diff 和 虚拟 DOM 的内存占用,你会感觉你的应用非常之轻量。

但是同时这也造成了新的问题,虚拟 DOM 是有其优点的,尤其是在中大型应用上各种组件和状态之间相互嵌套 Svelte 的更新策略导致它可能需要一个新的API 来标定是否需要更新。而且除非很小的项目,一般来说虚拟 DOM 的性能和原生的比差距不到百分之 20,为了这点性能要放弃掉虚拟DOM 的优势值不值呢?尤其是 Function 组件的强大抽象能力,更不要说还 服务器渲染,vdom 测试等虚拟 DOM 自带的优势能力。

真正的响应式编程

最好的 API 是没有 API, 在传统的框架中我们总是需要告诉组件库哪些是状态,或是声明,或是 hooks,更新状态也无法随心所欲,要么使用 this.xxx, 要么使用 setXXX,svelte 是没有 api 的,用起来和写 js 代码一样。

  1. <script>
  2. let count = 0;
  3. function handleClick() {
  4. count += 1;
  5. }
  6. </script>
  7. <button on:click={handleClick}>
  8. Clicked {count} {count === 1 ? 'time' : 'times'}
  9. </button>

之所以能做到这一点是因为它在编译的时候帮我们做掉了一些东西。

真正的优势

光凭以上的几点,很难解释为什么它这么受欢迎,其实它真正的卖点在于没有运行时。以下是我们常见的组件库的运行时代码,这意味着无论多小的组件,你仍然需要引入这些代码。
image.png
而 svelte 是几乎没有 runtime 的,不需要虚拟dom,不需要 diff 算法,编译出来之后就是 appendChild 之类的原生代码,这一点对于多技术栈的项目来说非常非常有优势,不内置 runtime 无法开箱即用,内置的话会导致包大小快速膨胀且多次依赖。

  1. /* App.svelte generated by Svelte v3.29.6 */
  2. import { SvelteComponent, detach, element, init, insert, noop, safe_not_equal } from "svelte/internal";
  3. function create_fragment(ctx) {
  4. let h1;
  5. return {
  6. c() {
  7. h1 = element("h1");
  8. h1.textContent = `Hello ${name}!`;
  9. },
  10. m(target, anchor) {
  11. insert(target, h1, anchor);
  12. },
  13. p: noop,
  14. i: noop,
  15. o: noop,
  16. d(detaching) {
  17. if (detaching) detach(h1);
  18. },
  19. };
  20. }
  21. let name = "world";
  22. class App extends SvelteComponent {
  23. constructor(options) {
  24. super();
  25. init(this, options, null, create_fragment, safe_not_equal, {});
  26. }
  27. }
  28. export default App;

svelte/internal 中是类似 jquery 的代码,可以抹平浏览器之间的差异提供兼容性。以 insert 为例

  1. export function append(target:Node, node:Node) {
  2. target.appendChild(node);
  3. }

可以说是非常轻量了,用 svelte 实现的 todo App 更是只有 3kb。

编译时框架

那么它是怎么实现响应式的呢,svelte 用了另外一种方式,直接在编译的时候帮用户做到这件事情,在 svelte 中你是这样写的

  1. count += 1;

编译完成之后就变成了

  1. count += 1; $$invalidate('count', count);

count 在修改的时候会增加一个 invalidate,invalidate 会调用 flush,而 flush 根据 传入的参数重新执行一下组件中生成 html 的代码
以 hello word 为例,我们的name 发生改变的时候 flush 会执行一下 p 来更新数据。

Svelte - 真正的 reactivity 库 - 图2

其他所有内容都是静态的,只有 name 可能会发生改变,这个 p 是一个 update 函数,它唯一做的事情就是当 name 发生变更的时候对它(name)进行更新。

那么代价是什么呢? svelte 生成的代码其实是以命令式的形式通过逐行插入创建了所有元素,并且它们有一个单独的函数进行更新操作。这个时候它反而不快了,因为 虚拟DOM 只需要一行代码。而且这种形态决定它只能使用模板语法,失去了 JSX 无敌的灵活性,尤其是把 DOM 当成状态来进行管理的能力。模板即限制,这一点在 Angular 中表现的更明显,不幸的是 svelte 也有同样的问题。

  1. insert(target, button, anchor);
  2. append(button, t0);
  3. append(button, t1);
  4. insert(target, t2, anchor);
  5. insert(target, p0, anchor);
  6. append(p0, t3);
  7. append(p0, t4);
  8. append(p0, t5);
  9. insert(target, t6, anchor);
  10. insert(target, p1, anchor);
  11. append(p1, t7);
  12. append(p1, t8);
  13. append(p1, t9);

而且编译路线会让你写的代码和实际执行的代码不是一样的东西,当然我们可以用精妙的工具来进行的各种容错处理,但是对于开发者来说你其实很难读懂你的代码做了什么, 你以为你在写 html,其实你再写 js。你无法深入到编译出来的代码中看到任何你熟悉的东西。

周边生态

svelte 的模板模式注定它需要很多的配套设施,代码高亮,TypeScript 支持,代码自动补全这些都是需要开发者去做的。作者也注意到了这个问题,一直在改进。在 vscode 中相关插件下载量已经非常可观,WebStrom 也专门做了一个插件来支持 svelte ,但是强大如 vue,在 2020 年仍然没有做好 typescript 的支持,入坑的时候仍然需要谨慎。
image.png

总的来说 svelte 是提供了一个新的思路来进行前端的开发,在 svelte@3 中这个观念已经非常成熟了,自然的响应式,对原生html,js,css的完美支持,自动的 stroe订阅,无 runtime 的编译产物,让其在 三大框架统一的时候仍然走出了自己的路。在写一些比较简单的,但是希望兼容任何框架的代码时,svelte 无疑是个很棒的选择。

参考文档