最近,我对比较框架和vanillaJavaScript变得非常感兴趣。它始于我在一些自由项目中使用React的一些挫折,以及我最近作为规范编辑器对Web标准的认识。我想了解这些框架的共性和差异是什么,Web平台必须提供什么作为更精简的替代方案,以及它是否足够。我的目标不是抨击这些框架,而是了解成本和收益,找出是否有替代方案,甚至当我们决定采用一个框架时,我们可以从中学习。在本系列文章的第一部分,我将深入探讨框架的一些共同技术特性,并描述几种不同的框架如何实现这些特性。我还会查看使用这些框架的成本。Framework我选择了四种框架进行研究。React是当今的主导框架,并且有三个较新的竞争者声称其工作方式与React不同。React:“React使创建交互式用户界面变得毫不费力。声明式视图使您的代码更可预测且更易于调试。”SolidJS:“Solid遵循与React相同的理念……但它以完全不同的方式实现,放弃了使用虚拟DOM。”Svelte:“Svelte是一种构建用户界面的全新方式……构建应用程序时发生的编译步骤。Svelte不使用虚拟DOM差异等技术,而是编写代码,在您的状态时通过外科手术更新DOM申请变更。”Lit:“在WebComponents标准的基础上,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)中的数据绑定:functionHelloWorld(){constname="SolidorReact";return(
Hello{name}!
)}Lit中的数据绑定:classHelloWorldextendsLitElement{@property()name='lit';render(){returnhtml`
Hello${this.name}!
`;}}Svelte中的数据绑定:
Hello{name}!
反应性反应性是一种表达变化传播的声明方式。如果我们能够以声明的方式表达数据绑定,那么我们必须有一种有效的方式让框架传播变化。React引擎将渲染结果与之前的结果进行比较,并将差异应用到DOM本身。这种处理变更传播的方式称为虚拟DOM。在SolidJS中,这是通过其商店和内置元素更明确地完成的。例如,Show元素将跟踪内部的变化,而不是虚拟DOM。在Svelte中,生成“活动”代码。Svelte知道哪些事件会导致更改,并且它会生成简单明了的代码来区分事件和DOM更改。在Lit中,反应性是通过元素属性来实现的,基本上依赖于HTML自定义元素的内置反应性。逻辑如果一个框架为数据绑定提供了一个声明性接口并支持反应性,那么它必须提供某种方式来表达传统意义上的某些逻辑,即命令式编写。逻辑的基本构建块是“if”和“for”,所有主要框架都提供了这些构建块的某种表达。(1)条件句除了绑定数字和字符串等基本数据外,每个框架都提供了一个“条件”原语。在React中,它看起来像这样:const[hasError,setHasError]=useState(false);返回hasError?
:null;...setHasError(true);SolidJS提供了内置的条件组件。
Svelte在Lit中提供了#if指令:{#ifstate.error}{/if},您将在渲染函数中使用显式三元运算:render(){returnthis.error?html``:null;}(2)列表另一个常见的框架原语是列表处理。列表是用户界面的关键部分——如联系人列表、通知等——并且为了高效地工作,它们必须是反应性的,而不是在一个数据项更改时更新整个列表。在React中,列表处理看起来像这样:contacts.map((contact,index)=>{contact.name})React使用特殊的键属性来区分列表项,确保整个列表不会在每次渲染时被替换。在SolidJS中,使用for和index内置元素:{contact=>{contact.name}
}ownStorage结合for和index来确定当项目发生变化时要更新哪些元素。它比React更干净,让我们避免了虚拟DOM的复杂性。Svelte使用each指令,根据其更新程序进行翻译:{#eachcontactsascontact}{contact.name}
{/each}Lit提供了一个重复功能,其工作方式类似于React的基于键的列表mapping:repeat(contacts,contact=>contact.id,(contact,index)=>html`${contact.name}
`组件模型有一件事超出了本文范围,那就是组件不同框架中的模型,以及如何使用自定义HTML元素处理它。注意:这是一个很大的话题,我想在以后的文章中介绍这个话题,因为这个话题会使这篇文章变得太长。成本框架提供声明性数据绑定、控制流原语(条件和列表)以及用于传播更改的反应机制。它们还提供其他重要的东西,例如重用组件的方法但这是另一篇文章的主题。框架有用吗?是的。它们给我们带来了所有这些方便的功能。但这是正确的问题吗?使用框架是有代价的.让我们来看看成本。捆绑包大小在查看捆绑包大小时,我更愿意看到非Gzip压缩后的大小。这个大小与JavaScript的CPU开销有很大关系:ReactDOM大约为120KB。SolidJS大约18KB。Lit大约为16KB。Svelte大约2KB,但生成的代码大小不同。目前的框架似乎在保持包大小方面比React更好。虚拟DOM需要大量的JavaScript。构建不知何故,我们习惯于“构建”Web应用程序。如果不设置像Node.js和Webpack这样的捆绑器,处理Babel-TypeScript启动包中的一些最近的配置更改,以及所有这些事情,就不可能开始一个前端项目。框架的表现力越强,包体积越小,但对构建工具和翻译时间的负担越大。Svelte声称虚拟DOM完全与开销有关。我同意,但也许像Svelte和SolidJS这样的“构建”以及像Lit这样的自定义客户端模板引擎只是纯粹的开销?在构建和转译过程中,调试也是一种不同的成本。当我们使用和调试网络应用程序时,我们看到的代码与我们编写的代码完全不同。我们现在依靠相同质量的调试工具对网站进行逆向工程并将其与我们自己代码中的错误相关联。在React中,调用堆栈永远不是“你的”东西——React会为你处理调度。当没有错误时,此功能非常有用。但是,如果您试图弄清楚为什么无限循环会重复出现,您将陷入痛苦的境地。在Svelte中,库本身的包大小很小,但您正在运送和调试一大堆神秘的生成代码,这些代码是Svelte的反应性实现,根据您的应用程序的需要定制。Lit不需要大量构建,但要有效调试,您必须熟悉其模板引擎。这也许是我对框架持怀疑态度的最大原因。当您寻找自定义声明式解决方案时,您将面临更困难的命令调试。本文中的示例使用TypeScript指定API,但代码本身不需要转译。升级在本文中,我讨论了四种框架,但还有许多其他框架,数不胜数(AngularJS、Ember.js和Vue.js,仅举几例)。你能指望框架、它的开发人员、它的想法和它的生态系统在开发过程中为你工作吗?比修补自己的错误更令人沮丧的事情之一是必须找到框架错误的解决方法。而且,还有一个更令人沮丧的事情,就是如果你不修改你的代码就将框架升级到新版本,就会出现bug。诚然,在浏览器中存在这样的问题,但是当出现这种问题时,它会影响到每个人,并且在大多数情况下,修复或解决方案是迫在眉睫的。另外,本文提到的大部分模式都是建立在成熟的网络平台API之上的,并不一定需要尖端的技术。总结我们对框架解决的核心问题有了更好的理解,重点是数据绑定、反应性、条件和列表。我们还讨论了成本。在本系列的第二部分中,我们将了解如何在没有框架的情况下解决这些问题,以及我们可以从中学到什么。敬请关注!原文链接:https://www.smashingmagazine.com/2022/01/web-frameworks-guide-part1/