
选择了四个框架来研究:React是当今占主导地位的框架,以及三个声称与React不同的新竞争者。
总结一下这些框架对它们的区别的看法:
框架本身提到了声明性、反应性和虚拟 DOM 等词。让我们深入了解这些含义:
声明式编程
声明式编程是一种范式,在这种范式中,逻辑被定义,而没有指定控制流。
我们描述需要的结果是什么,而不是哪些步骤能让我们达到目的。
在声明式框架的早期,大约在2010年,DOM的API更加赤裸裸和冗长,用命令式的JavaScript编写Web应用程序需要大量的模板代码。这时,“模型-视图-视图模型”(MVVM)的概念开始盛行,当时具有划时代意义的Knockout和AngularJS框架,提供了一个JavaScript声明层,在库内处理这种复杂性。
今天,MVVM并不是一个广泛使用的术语,它在某种程度上是旧术语 "数据绑定 "的变种。
数据绑定
数据绑定是一种声明性的方式来表达数据如何在模型和用户界面之间进行同步。
所有流行的UI框架都提供了某种形式的数据绑定,它们的教程都以数据绑定的例子开始。
这里是JSX(SolidJS和React)中的数据绑定。
| function HelloWorld() {const name = "Solid or React";
 | 
 return (
      <div>Hello {name}!</div>
  )
 }
 
Lit中Data-binding数据绑定 :
| class HelloWorld extends LitElement {@property()name = 'lit';
 | 
 render() {
    return html<p>Hello ${<b>this</b>.name}!</p>;
  }
 }
 
Svelte中数据绑定:
| <script>let name = 'world'; </script> | 
<h1>Hello {name}!</h1>
 
反应式
Reactivity是一种声明性的方式来表达数据改变的传播。
当我们有一种声明性地表达数据绑定的方式时,我们需要一种有效的方式来让框架传播数据改变。
逻辑
当一个框架为数据绑定提供了一个声明性的接口,并实现了反应性,它也需要提供一些方法来表达一些传统上必须写的逻辑。逻辑的基本构件是 "if "和 “for”,所有主要的框架都提供了这些构件的一些表达。
除了绑定基本数据如数字和字符串,每个框架都提供了一个 "conditional "语法。
在React中,它看起来像这样。
| const [hasError, setHasError] = useState(false); return hasError ? <label>Message</label> : null; … setHasError(true); | 
SolidJS提供了一个内置的条件组件,Show:
| <Show when={}><label>Message</label>
</Show>
 | 
Svelte提供了if指令:
| {#if }<label>Message</label>
{/if}
 | 
在Lit中,你会在渲染函数中使用明确的三元操作:
| 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={acts}>{contact => <DIV>{contact.name}</DIV> }
</For>
 | 
在内部,SolidJS使用它自己的存储与for和index相结合,以决定当项目发生变化时要更新哪些元素。
它比React更明确,使我们能够避免虚拟DOM的复杂性。
Svelte使用each指令,根据其更新器进行转译:
| {each contacts as contact}<div>{contact.name}</div>
{/each}
 | 
Lit提供了一个重复函数,它的工作原理类似于React的基于键的列表映射:
| repeat(contacts, contact => contact.id,(contact, index) => html`<div>${contact.name}</div>`
 | 
组件模型
有一件事超出了本文的范围,那就是不同框架中的组件模型,以及如何使用自定义HTML元素来处理它。
成本
框架提供了声明性的数据绑定,控制流原语(条件和列表),以及传播变化的反应式机制。
它们还提供了其他重要的东西,比如重用组件的方法,但这是另一篇文章的主题。
框架有用吗?是的。它们给了我们所有这些方便的功能。但这是一个正确的问题吗?使用框架是有代价的。让我们看看这些代价是什么。
捆绑包大小
在查看bundle size时,我喜欢看非Gzip的minified size。这是与JavaScript执行的CPU成本最相关的大小。
似乎今天的框架在保持捆绑大小方面做得比React更好。虚拟DOM需要大量的JavaScript。
构建
不知为何,我们习惯于 "构建 "我们的网络应用。如果不设置Node.js和Webpack这样的捆绑器,不处理Babel-TypeScript启动包中最近的一些配置变化,就不可能启动一个前端项目,以及所有这些事情。
框架的表现力越强,捆绑规模越小,构建工具和转译时间的负担就越大。
Svelte声称,虚拟DOM是纯粹的开销。
我同意,但也许 “构建”(如Svelte和SolidJS)和自定义客户端模板引擎(如Lit)也是纯粹的开销,是不同类型的?
调试
随着构建和转译的进行,会产生一种不同的代价。
当我们使用或调试网络应用时,我们看到的代码与我们写的完全不同。我们现在依靠不同质量的特殊调试工具来反向设计网站上发生的事情,并将其与我们自己代码中的错误联系起来。
当你寻找自定义的声明式解决方案时,你最终会面临更痛苦的命令式调试。本文中的例子使用Typescript来规范API,但代码本身并不要求转译。
升级
在本文中,我看了四个框架,但框架多得数不清(AngularJS、Ember.js和Vue.js,仅举几例)。你能指望框架、它的开发者、它的思想份额和它的生态系统在发展中为你工作吗?
有一件事比修复你自己的错误更令人沮丧,那就是必须为框架的错误找到变通办法。而比框架bug更令人沮丧的一件事是,当你把框架升级到新版本而不修改你的代码时,就会出现bug。
诚然,这个问题也存在于浏览器中,但当它发生时,它发生在每个人身上,而且在大多数情况下,修复或公布的变通方法是迫在眉睫的。另外,本文中的大多数模式都是基于成熟的网络平台API;并不总是需要走在流血的边缘。
总结
我们研究了使用框架的不同好处和成本,从他们试图解决的核心问题的角度出发,重点关注声明式编程、数据绑定、反应性、列表和条件。
Web 平台已经提供了开箱即用的声明式编程机制:HTML 和 CSS。这种机制是成熟的、经过良好测试的、流行的、广泛使用的和记录在案的。但是,它没有提供明确的数据绑定、条件渲染和列表同步的内置概念,并且反应性是跨多个平台功能传播的微妙细节。
保持DOM树的稳定实现反应性
在 ReactJS 和 SolidJS 中,我们创建了转换为命令式代码的声明性代码,将标签添加到 DOM 或删除它。在 Svelte 中,会生成该代码。
但是我们可以使用 CSS 来隐藏和显示错误标签:
| <style& { display: none; }.app. {display: block; }
</style>
<label class="error">Message</label>
 | 
<script>
    le(‘has-error’, true);
 </script>
 
在这种情况下,反应是在浏览器中处理的——应用程序的类更改会传播到其后代,直到浏览器中的内部机制决定是否呈现标签。
这种技术有几个优点:
面向表单的“数据绑定”
在 JavaScript 繁重的单页应用程序 (SPA) 时代之前,表单是创建包含用户输入的 Web 应用程序的主要方式。传统上,用户填写表单并单击“提交”按钮,服务器端代码将处理响应。表单是数据绑定和交互的多页应用程序版本。
在上一节的错误标签示例中,我们展示了如何反应性地显示和隐藏错误消息。这就是我们在 React 中更新错误消息文本的方式(在 SolidJS 中也是如此):
| const [errorMessage, setErrorMessage] = useState(null);
return <label className="error">{errorMessage}</label>
 | 
当我们拥有稳定的 DOM 和稳定的树形表单和表单元素时,我们可以执行以下代码替代使用这些框架:
| <form name="contactForm"><fieldset name="email"><output name="error"></output></fieldset> </form> | 
<script>
   function setErrorMessage(message) {
   value = message;
   }
 </script>
 
看起来非常冗长,但它也非常稳定、直接且非常高效。
输入数据的表单
正确使用表格,有一个简洁的替代方案:
| <form name="contactForm"><input name="id" type="hidden" value="136" /><input name="email" type="email"/><input name="name" type="string" /><input name="subscriber" type="checkbox" /> </form> | 
<script>
    updateContact(Object.fromEntries(
        new FormData(actForm));
 </script>
 
通过使用隐藏输入和有用的FormData类,我们可以在 DOM 输入和 JavaScript 函数之间无缝转换值。
结合表单和反应性
通过结合表单的高性能选择器稳定性和 CSS 响应性,我们可以实现更复杂的 UI 逻辑:
| <form name="contactForm"><input name="showErrors" type="checkbox" hidden /><fieldset name="names"><input name="name" /><output name="error"></output></fieldset><fieldset name="emails"><input name="email" /><output name="error"></output></fieldset> </form> | 
<script>
   function setErrorMessage(section, message) {
   actForm.elements[section].value = message;
   }
   function setShowErrors(show) {
   actForm.elements.showErrors.checked = show;
   }
 </script>
<style>
    input[name=“showErrors”]:not(:checked) ~ * output[name=“error”] {
       display: none;
    }
 </style>
 
请注意,在此示例中,没有使用类——我们从表单的数据中开发 DOM 的行为和样式,而不是通过手动更改元素类。
表单的优势 :
列表项的 HTML 模板元素
当我们使用一个template元素时,我们可以避免创建元素并在 JavaScript 中填充它们的所有样板代码。
| <ul id="names"><template><li><label class="name" /></li></template>
</ul>
<script>function addName(name) {const list = document.querySelector('names');const item = list.querySelector('template').content.cloneNode(true).firstElementChild;item.querySelector('label').innerText = name;list.appendChild(item);}
</script>
 | 
通过使用template列表项的元素,我们可以在原始 HTML 中看到列表项——它不是使用 JSX 或其他语言“渲染”的。您的 HTML 文件现在包含应用程序的所有HTML — 静态部分是呈现的 DOM 的一部分,而动态部分在模板中表示,准备好在时机成熟时克隆并附加到文档中。
TodoMVC是用于展示不同框架的 TODO 列表的应用程序规范。TodoMVC 模板带有现成的 HTML 和 CSS,可帮助您专注于框架。
您可以在 GitHub 存储库中使用结果,并且可以使用[url=]完整的源代码[/url]。
最佳实践
原文点击标题
本文发布于:2024-03-11 16:35:12,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/1710558886142312.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
| 留言与评论(共有 0 条评论) |