译者 | Ovie Okeh 翻译者 | 王磊
那个试题可能有点儿生硬,但无论如何 Svelte 和它的经营理念就是这样的。假如你还据我所知 Svelte 的话就去了解呵呵吧——你会缔造这场民主革命的,它将获得前所未有的创举(没给 Svelte 项目组增加压力的原意)。
责任编辑并非 Svelte 的入门讲义。Svelte 项目组已经做了这份很酷的互动式亲自动手入门讲义,能协助你随心所欲跨入积极响应式程式设计的四海。
Svelte 讲义门牌号: https://svelte.dev/tutorial/basics
先来份严格来说新闻稿吧:我并非程式设计剑客,我也没博学多闻。我罢了非常热衷每晚出现的技术创新表达方式,讨厌足不出户谈论它们罢了——这也是这首诗的出处。请主观看待我说的每一句话,假如我这儿跑了大巴也请告诉我吧。
那好,他们这就开始自问自答吧!
具体来说,聊聊 React探讨为甚么我认为 Svelte 有如此颠覆性的冲破以后,咱们先来看一看以后 Dan 发布的“React并并非完全积极响应式的”那条贴文,研究呵呵它另一面的涵义:
另两个严格来说新闻稿:责任编辑可没抨击 React 的原意。我罢了拿 React 来举个范例表明难题,即使绝大多数读者总有一天单厢加进它的。提 React 罢了即使它是跟 Svelte 对照的最差范例罢了。
Dan 下面此时此刻到底是甚么原意?这对他们现在写标识符的方式有甚么影响?要提问那个难题,我先单纯如是说呵呵 React 的工作监督机制吧。
当你呈现出两个 React 应用时,React 会在所谓虚拟 DOM 中保留 DOM 的副本。虚拟 DOM 充当你的 React 标识符与浏览器绘制到 DOM 的内容之间的中间层。
然后当你的数据出现变动时(可能即使你调用了 this.setState,useState),React 会做一些工作来确定如何在屏幕上重新绘制 UI。
它会对照虚拟 DOM 与真实的 DOM,以确定数据更新导致了哪些更改。然后它会仅重新绘制与虚拟 DOM 中的新副本不匹配的 DOM 部分,这样就无需在每次数据更新时重新绘制整个 DOM 了。
这就显著提升了性能,即使更新虚拟 DOM 比更新真实 DOM 要节省很多资源,而 React 只更新真实 DOM 中需要改变的部分。有一首诗很好地解释了这一过程: https://medium.com/@gethylgeorge/how-virtual-dom-and-diffing-works-in-react-6fc805f9f84e
但你可能会发现那个实现有点儿难题。假如你没告诉 React 你的数据已经改变了(比如说调用 this.setState 或 Hooks 之类),那么虚拟 DOM 就不会有变化,React 也不会随之积极响应(Duang!搞砸了!)。
这就是 Dan 所说的,React 并并非完全的积极响应式设计的原意。React 需要你手动跟踪应用数据,并在数据变化时告诉 React,这也意味着你得做更多工作。
好了,现在该谈 Svelte 了Svelte 是一种构建 UI 的全新途径,它速度极快、效率极高,是或者说的积极响应式设计,还不需要虚拟 DOM;用 Svelte 写的标识符比其它任何框架或库都更加简洁。
说得这么好听,可你肯定会问它和其它一大堆 JavaScript 库和框架到底有甚么区别呢?我来逐一表明吧,
或者说的积极响应式设计Svelte 既并非库也并非框架;相反,Svelte 是两个编译器,它吃进你的标识符并吐出与你的 DOM 直接交互的原生 JavaScript,不需要中间层。
等等,甚么?编译器?是的——编译器。那个思路太强悍了,我都不知道为甚么以前没人想得到呢?为甚么那个主意这么棒,听我细细道来吧。
引一句 Rich Harris 在 YGLF 2019 大会上的讲话:
Svelte 3.0 将积极响应设计从组件 API 移到了程式设计语言中。
这说的是啥?别急,他们已经看到 React 和绝大多数其他前端框架,要求你在更新其虚拟 DOM 以后,使用 API 来告诉它数据已更改(再次通过调用 this.setState 或 useState)。
在 React 以及绝大多数 UI 框架和库中,调用 this.setState 意味着你的应用的积极响应能力是与特定的 API 绑定的,没 API 它就没法知道数据甚么时候变动了。
Svelte 采取了另一种方法解决那个难题。
1. (() => {
2. const square => number => number * number;
3.
4. const secondNumber = square(firstNumber);
5. const firstNumber = 42;
6.
7. console.log(secondNumber);
8. })();
现在假如你按从上到下的顺序运行这几行标识符的话就会在第 4 行遇到错误,即使 secondNumber 依赖 firstNumber,而这时候 firstNumber 尚未初始化。
假如以拓扑顺序运行这段标识符则不会出现任何错误。为啥呢?编译器并不会按从上到下的顺序运行这段标识符;相反,它会查看所有变量并生成依赖图(比如说 A 依赖 B 才能工作之类)。
这算是对编译器如何以拓扑顺序编译标识符的简化解释了。
1. 那个新变量square是否依赖其它变量?
– 它没,所以我会初始化它
2. 那个新变量secondNumber是否依赖其它变量?
– 它依赖square和firstNumber。我已初始化square,但我还没初始化firstNumber,马上就会做。
3. 好的,我已初始化firstNumber。现在我可以使用square和firstNumber初始化secondNumber了
– 我是否拥有运行此 console.log 语句所需的所有变量?
– 是的,所以我会运行它了。
乍看上去标识符好像是从上到下的运行顺序,但仔细观察就会发现它的确是跳着执行的。
跑到第 4 行时,编译器发现它没 firstNumber,因此会暂停执行并查看标识符,找出它是并非在别的地方定义了。一看,原来它是在第 5 行定义的,所以编译器会先运行第 5 行,然后返回第 4 行继续执行。
假如语句 A 依赖于语句 B,则语句 B 会先运行,运行顺序与新闻稿的顺序无关。
那么这和 Svelte 实现真正的积极响应式设计又有甚么关系?具体来说,你可以在 JavaScript 中用标识符标记两个语句,如下所示:$: foo = bar。它会在 foo = bar 语句中添加两个名为 $ 的标识符(假如以后未定义 foo,则严格模式下会出错)。
变量。
积极响应!这意味着他们现在正在使用 JavaScript 的 API 核心部分来实现或者说的积极响应设计,无需摆弄像 this.setState 这样的第三方 API。
实践中是那个样子:
1. // vanilla js
2. let foo = 10;
3. let bar = foo + 10; // bar is now 20
4. foo = bar // bar is still 20 (no reactivity)
5. bar = foo + 10 // now bar becomes 30
6. // svelte js
7. let foo = 10;
8. $: bar = foo + 10; // bar is now 20
9. foo = 15 // bar is now 25 because it is bound to the value of foo
请注意,在下面的标识符中他们不需要将 bar 重新分配给 foo 的新值——比如直接通过 bar = foo + 10;或者通过调用像 this.setState({ bar = foo + 10 }); 这样的 API 方法,现在都用不着了。它会自动为他们处理好的。
这意味着当你将 foo 更改为等于 15 时,bar 会自动更新为 25,并且你不必调用 API 来为你更新它。Svelte 已经知道了。
下面的 Svelte 标识符的编译版本如下所示:
1. … omitted for brevity …
2. function instance($$self, $$props, $$invalidate) {
3. let foo = 10; // bar is now 20
4. $$invalidate(foo, foo = 15) // bar is now 25 because it is bound to the value of foo
5. let bar;
6. $$self.$$.update = ($$dirty = { foo: 1 }) => {
7. if ($$dirty.foo) { $$invalidate(bar, bar = foo + 19); }
8. };
9. return { bar };
10. }
11. … omitted for brevity …
好好花点时间研究呵呵下面这段标识符吧,慢慢来,不要着急。
看到在 bar 被定义以后 foo 是如何更新的了吗? 那是即使编译器正在以拓扑顺序,而非严格的自上而下的顺序在解析 Svelte 标识符。
Svelte 会自己积极响应数据变化。它用不着你操心更改的内容和时间;它自己就会知道。
注意: 在第 4 行里,bar 的值到下两个 Event Loop 以后都不会更新的,这样一切单厢干净又整洁。
这样你就不必在数据发生变化时手动更新状态了。你可以专注于你的标识符逻辑,而 Svelte 可以协助你将 UI 与最新状态协调好。
简洁前面我并非说 Svelte 可以用更少的标识符来完成更多工作吗?事实确实如此。下面我拿 React 中两个单纯的组件和 Svelte 中的对应组件举个范例,你自己看:
17 行对 29 行标识符,这俩应用的功能完全相同,看一看他们在 React.js 中编写了很多的标识符吧——这我还没开始用 Angular 呢。
Svelte 标识符除了更简洁耐看外也更容易理解,即使它的活动部件比 React 标识符少。他们不需要事件处理程序来更新输入元素的值——只需绑定值即可。
回想你刚刚开始学习网页开发的时候。哪边的标识符会让你更难理解?左边的还是右边的?
虽然这看起来没那么重要,但当你开始构建更大、更复杂的应用时,很快就会发现不用写那么多标识符是多么有用。我曾花了好几个小时试图理解同事编写的大型 React 组件是如何工作的。
我确实相信 Svelte 的简化 API 能使他们更快地阅读和理解标识符,从而提高整体工作效率。
性能好了,现在他们已经知道 Svelte 是或者说的积极响应式设计,可以让你用更少的投入做更多的事情。那么它的性能如何?完全用 Svelte 编写的应用能有很好的用户体验吗?
React 之所以如此强大,其原因之一在于它使用虚拟 DOM 来更新应用程序的 UI,一次只更新一部分,无需在每次更改内容时重新构建整个 DOM(这非常消耗资源)。
但这种方法的缺点是,假如组件的数据发生变化,React 将重新渲染该组件及其所有子组件,哪怕子组件不需要重新渲染也得这么干。这就是为甚么 React 会有 shouldComponentUpdate、useMemo、React.PureComponent 一类的 API。
只要使用虚拟 DOM 在状态更改时渲染 UI,那个难题就没法解决。
Svelte 不使用虚拟 DOM,那么它如何解决重新绘制 DOM 以匹配应用程序状态的难题呢?这里我再次引用 Rich Harris 在 YGLF 上的精彩演讲:
框架并非用于组织标识符的工具。它们是组织你思想的工具。
Rich 认为框架可以在构建步骤中运行,从而让标识符在运行时无需中间层。这也就是为甚么 Svelte 是编译器而非框架的原因。
这就是为甚么 Svelte 速度飞快的原因。Svelte 将你的标识符编译为两个直接与 DOM 交互的高效底层标识符。但 Svelte 是如何解决数据更改时重新绘制整个 DOM 的难题呢?
像 React 这样的框架需要你调用 API 方法,在数据发生变化时告诉它;但使用 Svelte 时,只需使用赋值运算符 = 就足够了。
假如状态变量——比如说 foo ——使用 = 运算符更新,则 Svelte 将仅更新依赖 foo 的其它变量,如前所示。这让 Svelte 可以仅绘制 DOM 的一部分内容,这些部分以
我将省略实际的实现方式,即使这首诗已经足够长了。你可以看一看 Rich Harris 自己的解释: https://www.youtube.com/watch?v=AdNJ3fydeao
结语Svelte 3.0 是最近软件开发业的福音之一。有些人可能会说这是夸大其词,但我不这么认为。Svelte 另一面的经营理念及其实现将使他们能向浏览器发送更少的 JS 模版,却做更多的事情。
反过来,这会带来性能更强、更轻量的应用程序,并生成更易阅读的标识符。那么现在,Svelte 将很快取代 React、Angular 或其它流行前端框架吗?
现在我可以说答案是否定的。与它们相比 Svelte 相对年轻,所以它需要时间来成长、成熟,并解决一些他们可能还没发现的难题。
就像 React 诞生后改变了软件开发产业一样,Svelte 也有可能改变他们对框架的看法,以及我们开发新表达方式时的思路。
英文原文: https://blog.logrocket.com/truly-reactive-programming-with-svelte-3-0-321Ωb49b75969
活动推荐GMTC 北京大会限额免费专场——多端提效与质量优化实践专场,将重点如是说贝壳找房过去快速发展的一年,他们在 toB 、toC 业务前端开发中,采取了哪些极限性能优化技术来提升业务体验,在 Node 服务稳定性方面的有哪些实践经验;讲述在 Native、Flutter、JS 等多场景下的质量监控预警以及快速定位难题方法;同时如是说贝壳找房在 Flutter 跨平台技术的架构设计、自动化集成、多业务解耦协作提效等方面的积累探索~