Web 框架能解决什么问题?

开发 架构
本文将深入探讨一些框架的共性技术特性,并介绍几种不同的框架是怎样实现这些特性的,我还要看一下使用这些框架的成本。

最近,我对对比框架和普通的 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”,而所有的主流框架都提供了这些构件的一些表达。

(1) 条件句

除了绑定数字和字符串等基本数据外,每个框架都提供了一个“条件”原语。在 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 指令:

{#if state.error}
<label>Message</label>
{/if}

在 Lit 中,你将在 render 函数中使用显式三元运算:

render() {
return this.error ? html`<label>Message</label>`: null;
}

(2) 列表

另一个常见的框架基元是列表处理。列表是用户界面的一个关键部分——如联系人列表、通知等——要想高效工作,就必须有反应性,而不是在一个数据项发生变化时,对整个列表进行更新。

在 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 之上,并不一定都需要采用尖端技术。

总结

我们对框架所要处理的核心问题有了更深刻的理解,并且着重于数据绑定、反应性、条件和列表。我们也对成本进行了讨论。

在本系列的第二部分中,我们将会了解到,在没有框架的情况下,我们是怎样处理这些问题的,以及我们可以从中学习到什么。敬请关注!

原文链接:https://www.smashingmagazine.com/2022/01/web-frameworks-guide-part1/

责任编辑:赵宁宁 来源: 前端之巅
相关推荐

2022-06-29 07:49:42

云存储架构DevOps

2020-06-15 08:06:25

ES数据

2019-04-26 13:01:16

ServiceMesh微服务架构

2020-05-22 10:02:43

Python语言编程

2021-10-16 12:52:17

Builder模式生成器

2021-07-16 06:56:50

边缘计算分布式

2011-11-30 15:28:32

在线协作系统

2021-05-11 10:56:07

DevOps开发工具

2024-11-04 10:28:08

2014-09-28 10:28:59

Docker云计算

2023-11-08 14:03:47

数据可视化数字化转型

2024-12-09 09:30:00

适配器模式设计模式代码

2023-05-31 07:32:37

2013-12-18 10:09:12

SVCHOST进程Windows Upd

2020-11-02 13:25:45

Redis数据库开源

2024-11-05 08:16:04

HTTP/3HTTP 2.0QUIC

2020-03-23 07:15:35

物联网IOT物联网技术

2021-12-15 23:42:56

Webpack原理实践

2009-08-04 17:27:18

Actor模型

2024-01-18 16:19:31

数据治理AI疲劳数据安全
点赞
收藏

51CTO技术栈公众号