最近,我对对比框架和普通的 JavaScript 产生了浓厚的兴趣。这始于我在一些自由职业项目中使用 React 时遇到的一些挫折,以及我最近作为规范编辑,对 Web 标准有了更多的认识。
我希望了解一下这些框架的共性和差异,Web 平台作为一种更精简的选择,能提供什么,以及它是否足够。我的目标并非要抨击这些框架,而是要了解成本和效益,找出有没有其他选择,甚至当我们决定采用框架时,我们也能从中吸取教训。
在本系列文章的第一部分中,我将深入探讨一些跨框架的共性技术特性,并介绍几种不同的框架是怎样实现这些特性的。我还要看一下使用这些框架的成本。
我选取四种架构进行研究。React 是当今的主流框架,还有三个较新的竞争者,它们声称自己的工作方式与 React 不同。
React
“React 使创建交互式用户界面变得不费力。声明性视图使你的代码更可预测,更容易调试。”
SolidJS
“Solid 遵循与 React 相同的理念……但它的实现方式完全不同,放弃了使用虚拟 DOM。”
Svelte
“Svelte 是一种全新的构建用户界面的方式……是一个在你构建应用时发生的编译步骤。Svelte 不使用虚拟 DOM diffing 之类的技术,而是编写代码,当你的应用程序的状态发生变化时,外科手术式地更新 DOM。”
Lit
“在 Web Components 标准的基础上,Lit 增加了……反应性、声明性模板,以及一些深思熟虑的特性。”
总结一下这些框架对其差异化的说法。
React 通过声明式视图使构建 UI 更容易。
SolidJS 遵循 React 的理念,但是采用了另一种技术。
Svelte 处理用户界面采用了一种编译时的方式。
Lit 使用现有的标准,并增加了一些轻量级的特性。
框架自身也提及了诸如声明性、反应性和虚拟 DOM 等词。让我们深入了解它们的含义。
声明性编程是一种范式,在这种范式中,逻辑被定义,而没有指定控制流。我们描述需要的结果是什么,而不是我们会采取什么步骤。
在 2010 年左右,声明性框架的早期,DOM 的 API 更加简单,更加冗长。而使用命令式的 JavaScript 编写 Web 应用程序则需要大量的模板代码。这时,“模型 - 视图 - 视图模型”(model-view-viewmodel,MVVM)的概念开始盛行,当时具有划时代意义的 Knockout 和 AngularJS 框架,提供了一个 JavaScript 声明层,在库内处理这种复杂性。
今天,MVVM 并不是一个广泛使用的术语,它在某种程度上是旧术语“数据绑定”的变种。
数据绑定是一种声明性的方式,用来表示数据如何在模型和用户界面之间同步。所有流行的 UI 框架都提供了某种形式的数据绑定,它们的教程都以数据绑定的例子开始。
以下是 JSX(SolidJS 和 React)中的数据绑定:
function HelloWorld() {
const name = "Solid or React";
return (
<div>Hello {name}!</div>
)
}
Lit 中的数据绑定:
class HelloWorld extends LitElement {
@property()
name = 'lit';
render() {
return html`<p>Hello ${this.name}!</p>`;
}
}
Svelte 中的数据绑定:
<script>
let name = 'world';
</script>
<h1>Hello {name}!</h1>
反应性是一种声明性的方式来表达更改的传播。
如果我们能够用一种声明的方式来表示数据绑定,那么我们就必须要有一个使框架能够传播更改的高效方法。
React 引擎会把渲染的结果与之前的结果相比较,并将差异应用于 DOM 本身。这种处理更改传播的方式,被称为虚拟 DOM。
在 SolidJS 中,这是以其存储和内置元素更明确地完成的。例如,Show 元素将跟踪内部的变化,而不是虚拟 DOM。
在 Svelte 中,生成“active”代码。Svelte 知道哪些事件会导致变化,它会生成直接的代码,区分事件和 DOM 更改。
在 Lit 中,反应性是通过元素属性来实现的,基本上是依赖 HTML 自定义元素的内置反应性。
如果框架为数据绑定提供了声明性的接口,并且能够实现反应性,那么就必须提供一些方法来表达一些传统意义上的逻辑,这些逻辑是以命令的方式写的。逻辑的基本构件是 “if” 和 “for”,而所有的主流框架都提供了这些构件的一些表达。
除了绑定数字和字符串等基本数据外,每个框架都提供了一个“条件”原语。在 React 中,它看起来如下所示:
const [hasError, setHasError] = useState(false);
return hasError ? <label>Message</label> : null;
…
setHasError(true);
SolidJS 提供了内置的条件组件。
<Show when={state.error}>
<label>Message</label>
</Show>
Svelte 提供了 #if 指令:
{
<label>Message</label>
{/if}
在 Lit 中,你将在 render 函数中使用显式三元运算:
render() {
return this.error ? html`<label>Message</label>`: null;
}
另一个常见的框架基元是列表处理。列表是用户界面的一个关键部分——如联系人列表、通知等——要想高效工作,就必须有反应性,而不是在一个数据项发生变化时,对整个列表进行更新。
在 React 中,列表处理看起来像这样:
contacts.map((contact, index) =>
<li key={index}>
{contact.name}
</li>)
React 使用特殊的 key 属性来区分列表项,它确保整个列表不会在每次渲染时被替换。
在 SolidJS 中,使用了 for 和 index 内置元素。
<For each={state.contacts}>
{contact => <DIV>{contact.name}</DIV> }
</For>
在内部,SolidJS 将自身的存储与 for 和 index 相结合,以确定在项目发生个更改时要更新哪些元素。它比 React 更清晰,使我们能够避免虚拟 DOM 的复杂性。
Svelte 使用 each 指令,该指令根据其更新器被转译:
{#each contacts as contact}
<div>{contact.name}</div>
{/each}
Lit 提供了一个 repeat 函数,它的工作原理类似于 React 的基于键的列表映射:
repeat(contacts, contact => contact.id,
(contact, index) => html`<div>${contact.name}</div>`
有一件事超出了本文的范围,那就是不同框架中的组件模型,以及如何使用自定义 HTML 元素来处理它。
注意:这是一个很大的主题,我想在以后的文章里讨论这个主题,因为这个主题会让这篇文章变得太长。
框架提供了声明性的数据绑定、控制流原语(条件和列表),以及传播更改的反应性机制。它们还提供了其他重要的东西,比如重用组件的方法,但这就是另一篇文章的主题了。
框架有用吗?是的。它们带给了我们所有这些方便的特性。但这是一个正确的问题吗?使用框架需要付出一定的成本。让我们来看一下这些成本。
在查看包大小时,我更愿意看到非 Gzip 的缩减大小。这个尺寸与 JavaScript 的 CPU 开销有很大关系。
ReactDOM 大约是 120 KB。
SolidJS 大约是 18KB。
Lit 大约是 16KB。
Svelte 约为 2KB,但生成的代码大小不同。
现在看来,在保持包大小上,现在的框架要优于 React。虚拟 DOM 要求使用很多 JavaScript。
不知何故,我们习惯了“构建” Web 应用。如果不设置 Node.js 和 Webpack 这样的捆绑器,不处理 Babel-TypeScript 启动包中最近的一些配置更改,以及所有这些事情,就不可能启动一个前端项目。
越是有表达力的框架,包大小就会变得更小,但构建工具和转译时间的负担就越大。
Svelte 宣称,虚拟 DOM 完全是一种开销。我同意,但是可能像 Svelte 和 SolidJS 这样的“构建”以及像 Lit 这样的自定义客户端模板引擎都只是单纯的开销吗?
在构建和转译过程中,需要付出的成本也是不同的。
我们在使用和调试 Web 应用程序时,所见到的代码和我们所编写的完全不一样。我们现在依靠同样品质的调试工具,逆向设计出一个站点,并把它和我们自己的代码中的 bug 相关联。
在 React 中,调用栈从来不是“你的”事情——React 会为你处理调度。这一特性在没有 bug 的时候非常好用。但是,如果你试图找出无限循环重现的原因,你将会陷入痛苦的境地。
在 Svelte 中,库本身的包大小很小,但你要传输和调试一大堆神秘的生成代码,这些代码是 Svelte 对反应性的实现,根据你的应用需求定制。
Lit 并不需要进行大量的构建,但是要想有效地进行调试,你就必须熟悉其模板引擎。这也许是我对框架持怀疑态度的最大原因。
当你寻求自定义的声明式解决方案时,你将面对更加困难的命令调试。本文中的示例采用了 TypeScript 来对 API 进行规范,但是该代码本身并不需要转译。
在本文中,我讨论了四个框架,但是还有许多其他的框架,多得数不清(AngularJS、Ember.js 和 Vue.js,仅举几例)。你能指望框架、它的开发者、它的思想和它的生态系统在开发过程中为你工作?
除了修补自己的 bug 之外,还有一个更让人沮丧的事情,就是必须为框架的错误找到变通方法。而且,还有一个更加令人沮丧的事情,那就是在没有修改你的代码的情况下,将框架升级为新的版本,会出现 bug。
诚然,浏览器中也有这样的问题,但是这种问题一旦出现,就会影响到所有人,而且在大多数情况下,修复或者发布一个解决方案,都是迫在眉睫的。此外,本文提到的大部分模式都建立在成熟的 Web 平台 API 之上,并不一定都需要采用尖端技术。
我们对框架所要处理的核心问题有了更深刻的理解,并且着重于数据绑定、反应性、条件和列表。我们也对成本进行了讨论。
在本系列的第二部分中,我们将会了解到,在没有框架的情况下,我们是怎样处理这些问题的,以及我们可以从中学习到什么。敬请关注!
Noam Rosenthal,Web 平台顾问,WebKit 和 Chromium 的贡献者,标准编辑,也是经验丰富的 Web 开发者。他的工作主要是在 Web 开发和浏览器 / 标准开发之间架起桥梁。
原文链接:
https://www.smashingmagazine.com/2022/01/web-frameworks-guide-part1/
互联网企业给被裁员工发“毕业须知”;孟晚舟担任华为轮值董事长;腾讯员工被曝偷看创业公司工作文档 | Q资讯
活动推荐
长期征集|寻找中国卓越技术团队
2022 年第一季《中国卓越技术团队访谈录》即将上线,本期精选了包括腾讯云鼎实验室、优麒麟、PingCAP、西门子 Mendix、火山引擎 ByteHouse、搜狗输入法无障碍产品在内的优秀团队,敬请关注。同时,访谈录现开放长期报名通道,如果你身处传统企业经历了数字化转型变革,或者正在互联网公司进行创新技术的研发,并希望 InfoQ 可以关注和采访你所在的技术团队,就请抓住机会吧!
点个在看少个 bug 👇
React.js(React)是 Facebook 推出的一个用来构建用户界面的 JavaScript 库。
Facebook开源了React,这是该公司用于构建反应式图形界面的JavaScript库,已经应用于构建Instagram网站及 Facebook部分网站。最近出现了AngularJS、MeteorJS 和Polymer中实现的Model-Driven Views等框架,React也顺应了这种趋势。React基于在数据模型之上声明式指定用户界面的理念,用户界面会自动与底层数据保持同步。与前面提及 的框架不同,出于灵活性考虑,React使用JavaScript来构建用户界面,没有选择HTML。Not Rest