前言

随着实习的告一段落,即将迎来本科阶段的最后一个学期。

在这之前,还有一项非常重要的工作要做:毕业设计选题。稍加思索,虽然未来研究生阶段会进行边缘计算方面的学习,但是最终还是选择了将自己的毕业设计定题在前端的领域,来为我本科阶段画一个句号。🛫

在这里非常感谢 Mirone 大佬为我引出前端如此新颖的技术栈和无限多的可能性,在这里也给大佬的 Github 项目做个小推广,一年不到就收割了 5k star 的在线 markdown 编辑器:Saul-Mirone/milkdown: 🍼 Plugin driven WYSIWYG markdown editor framework. (github.com)

作为一次简单的初探,本文会借鉴包括官方文档在内的许多线上资源,会依次放入最后的参考文档列表中。

Svelte

Svelte • Cybernetically enhanced web apps

官网的首页就写着这个框架最自豪的三大特点(感觉是故意地在针对市场主流的那两个框架一样

  1. write less code
  2. no virtual dom
  3. truly reactive

在描述自己的时候,Svelte 甚至也强调了自己不像 React 和 Vue 那样的「传统框架」一样将大量的工作让浏览器去完成,而是在编译阶段处理。

听起来有点像 Web Component?

作者分别针对他所自豪的三大特点写了三篇博文来进行具体的阐述。

更少的代码

更少的代码量意味着什么?作者给出了他的答案:更少的 BUG 和更少的项目开发时间

确实,为了能实现同样的功能,如果代码能写的更简洁,在一屏高度以内的组件代码可读性也会更加的高。

为了证明能比 Raect 和 Vue 用更短的代码实现同样的功能,最有力的方法当然是写个例子。

作者用那个经典「计数器」来证明了这一点。

1
2
3
4
5
6
7
8
9
<script>
let a = 1;
let b = 2;
</script>

<input type="number" bind:value={a}>
<input type="number" bind:value={b}>

<p>{a} + {b} = {a + b}</p>

在这里就不贴 React 和 Vue 版本的计数器了,博文里统计了三个版本计数器所需要的字符数。

  • React: 442
  • Vue: 263
  • Svelte: 145

绝了,看到这里我就心动了,简直是偷懒神器了

除了这个经典样例以外,作者也通过痛点分析(竞品?)的方式指出了 React 和 Vue 存在的几个问题:

  1. 对于 React 和 Vue 而言,他们只允许有一个顶层元素,即使有着 <></><template> 来去包裹多个元素,这种解决方式也是冗余且不优雅的。
  2. 在 binding 上,React 必须要手动处理包括 input 在内的许多事件,把 value={a}setA(a + 1) 这两个逻辑相关的代码存放在两个相距很远的地方。Vue 则需要标注成 v-model.number 来确保变量是以数值的形式保存的。
  3. 状态管理方面,React 使用 useState,对于每一个状态而言都要用一个变量和另一个保存状态的函数去进行更新操作。Vue 则需要导出一个 data 函数。
  4. 再见👋模板文件,告别了 React 的 useMemo, useCallbackuseEffect。Svelte 作为一个编译器,可以在很多方面脱离 JavaScript 的限制,开发时候更多的使用变量,而不是 proxies 和 hooks。

告别 Virtual DOM

Virtual DOM is pure overhead (svelte.dev)

在阅读这篇文章之前,我确实觉得 Virtual DOM 就是未来的趋势了,不过作者非常明确地告诉我们,Virtual DOM 并不是必须的,相反,操作 Real DOM 也可以带来同样甚至更高的 Performance。

在 2013 年的一个演讲中,React 的开发者之一 Pete Hunt 提出,直接操作 DOM 会有许多 performance 上的工作,非常的缓慢。自此之后,就掀起了对 Virtual DOM 的狂热 (meme) 之中。

This is actually extremely fast, primarily because most DOM operations tend to be slow. There’s been a lot of performance work on the DOM, but most DOM operations tend to drop frames.

但是 Svelte 的作者指出,这种性能上的提升仅仅只能和过去的框架或者说是跟稻草人 (a straw man) 在做比较。因为以前的框架意味着每次子状态的更新,都会 rerender 整个 app。

Pete 也在那次发布会之后澄清说,React 不是魔法,只是让你开发的时候不需要考虑性能的问题,并且在默认状态下就够快。

作者尝试分析了对于一个 DOM 树来说,什么才是经常性开支 (overhead)。

diffing isn’t free.

意思是,在更新一个结点时候,要判断更新的部分的筛选工作是非常昂贵的。

1
2
3
4
5
6
7
function HelloMessage(props) {
return (
<div className="greeting">
Hello {props.name}
</div>
);
}

对于上面这个组件来说,如果我们更新 props.name = "world",那么需要执行以下三步:

  1. 新旧的两个 snapshots (快照) 都包含了 <div>,所以更新的时候要保留 DOM 结点。
  2. 遍历新旧的所有属性,发现两者的 className 都等于 greeting
  3. 深入到结点的内层,发现结点的 text 改变了,所以更新 real DOM。

在这三步中,只有第三步才是真正有价值的,有没有办法只进行第三步而不需要前两步呢?如果我们能在变更 props.name 的时候,直接去进行第三步。

作者指出,Svelte 是这么做的:

1
2
3
if (changed.name) {
text.data = name;
}

除了在 diff 上有优化以外,React 还有一点是让人不满意的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function MoreRealisticComponent(props) {
const [selected, setSelected] = useState(null);

return (
<div>
<p>Selected {selected ? selected.name : 'nothing'}</p>

<ul>
{props.items.map(item =>
<li>
<button onClick={() => setSelected(item)}>
{item.name}
</button>
</li>
)}
</ul>
</div>
);
}

在这个例子中,这个父组件会通过遍历 props.items 生成了许多子 virtual DOM。这就导致在更新父组件的状态的时候,无论 props.items 变化与否,最终都会重新生成一次子列表。只有对性能要求极高的人才会发现或者愿意去进行优化。

React Hooks doubles down on defaulting to doing unnecessary work, with predictable results.

Svelte 也在这点上做出了优化,避免开发者陷入这种境况。

重新思考 Reactivity

Svelte 3: Rethinking reactivity

强烈推荐 Svelte 作者在 2019 年推出 Svelte 3 的时候的这期讲座:Rich Harris - Rethinking reactivity - YouTube,FANTASTIC!!🤩

Reactivity 该怎么翻译呢,反应能力?反应程度?

Anyway,我简单的理解就是,当一个变量发生变化的时候,其产生的化合作用同步使整个程序发生变化的能力。

例如在 React 中,我们使用 useState 钩子函数来进行状态管理,已实现状态和结点显示的内容能同步发生变化。

在 Svelte3 中,完全可以跳过这样的钩子函数,用不到 proxy 和 accessory,直接通过一行代码实现。

1
count += 1;

会被编译成:

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

RXJS

说实话,比较难理解,适用范围可能也相对不那么广。

RxJS - Introduction,一开始是 Microsoft 发布的一款开源库,后来由社区维护。

这是一个 JavaScript 的库,用来处理一些「异步」和「基于事件」的编程。结合了「观察者模式」、「迭代器模式」和「使用集合的函数式编程」。

在这个库里有几个基础的概念,我这边就直接复制粘贴中文站的翻译:

  • Observable (可观察对象): 表示一个概念,这个概念是一个可调用的未来值或事件的集合。
  • Observer (观察者): 一个回调函数的集合,它知道如何去监听由 Observable 提供的值。
  • Subscription (订阅): 表示 Observable 的执行,主要用于取消 Observable 的执行。
  • Operators (操作符): 采用函数式编程风格的纯函数 (pure function),使用像 mapfilterconcatflatMap 等这样的操作符来处理集合。
  • Subject (主体): 相当于 EventEmitter,并且是将值或事件多路推送给多个 Observer 的唯一方式。
  • Schedulers (调度器): 用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,例如 setTimeoutrequestAnimationFrame 或其他。

从官网的例子大概能看出来它的主要作用,就是将许多功能包装好,让开发人员可以自由添加到事件处理中去,包括点击的时候更新数组,节流,去重等等。

ReasonML

ReasonML - React as first intended (imaginarycloud.com),Facebook 用来开发 React 的一项技术。

一开始,Facebook 在开发 React 原型的时候,用的是一个叫做 Stand ML 的编程语言,后来由于语言的小众性,在开发完原型后使用 JavaScript 进行开发。

在一段时间内,一款叫做 BuckleScript 的编译器被发明了出来,可以将原始的 Ocaml 快速地编译成 JavaScript。

1
2
3
4
5
6
(* A Factorial implementation in BuckleScript / O'Caml *)
let rec factorial n =
if n <= 0 then
1
else
n * fact(n-1)

鉴于 React 的热度,Facebook 的开发团队决定将 OCaml 的语义,JavaScipt 的语法和 BuckleScript 的后端编译功能结合起来,创建了一个新的编译器,一种新的编程语言 ReasonML。

1
2
3
4
5
6
7
8
9
10
11
12
// A FizzBuzz implementation in ReasonML
let fizzbuzz = (i) =>
switch ([i mod 3, i mod 5]) {
| [0, 0] => "FizzBuzz"
| [0, _] => "Fizz"
| [_, 0] => "Buzz"
| _ => string_of_int(i)
};

for (i in 1 to 100) {
print_endline(fizzbuzz(i));
};

这门语言是和 JS 如此的相似,以至于某些代码可以写的完全相同。

1
2
3
// Both valid ReasonML and Javascript code
let add = (a, b) => a + b;
add(4, 6);

除此之外,React 的开发团队也制作了一款包装器,叫做 React Reason,可以在一个 React 应用或者 Reason 应用中将 JS 的组件和 Reason 的组件结合起来。

Redux 作为一款热门的状态管理库,在 ReactReason 中也不被需要了,因为 ReactReason 的无状态组件自带了 reducer。

cover (1)

参考文档

Svelte • Cybernetically enhanced web apps

Virtual DOM is pure overhead (svelte.dev)

Svelte 3: Rethinking reactivity

RxJS - Introduction

What & Why · Reason (reasonml.github.io)

ReasonML - React as first intended (imaginarycloud.com)