原文链接: medium.com/dailyjs/the…
I’ve seen many, many people using a modern framework like React, Angular or Vue.js blindly. These frameworks provide lots of interesting things, but usually people miss the point about the deepest reason of their existence, which is not:
They are based on components;
They have a strong community;
They have plenty of third party libraries to deal with things;
They have useful third party components;
They have browser extensions that help debugging things;
They are good for doing single page applications.
我见过很多人盲目地使用 React、Angular 或 Vue.js 等现代框架。这些框架提供了许多有趣的东西,但人们通常忽略了它们存在的最深层原因,这不是:
-
他们是基于组件的;
-
他们有一个强大的社区;
-
他们有很多第三方库来处理事情;
-
他们有有用的第三方组件;
-
他们有浏览器扩展来帮助调试;
-
他们适用于单页面应用程序。
The essential, fundamental, deepest reason is this one:
Keeping the UI in sync with the state is hard
最基本、最根本、最深刻的原因是:
保持UI与状态同步是很困难的
Yes, that’s it, and let’s see why
Imagine you are implementing a web application where the user can invite many people by introducing their email addresses. The UX/UI designer makes this decision: there is an empty state where we show a label with some help text, in any other case we show the email addresses with a button/link to delete them to the right.
假设你正在实现一个 web 应用程序,用户可以通过介绍他们的电子邮件地址来邀请很多人。UX/UI 设计师做出了这样的决定: 在一个空状态中,我们显示了带有帮助文本的标签,在其他情况下,我们在右侧显示带有删除按钮/链接的电子邮件地址。
The state of this form can be basically an array of objects containing the email addresses plus a unique identifier. Initially it will be empty. When adding an email address by typing it and hitting enter you add the typed email address to the array and update the UI. When the user clicks on “delete” you delete the email address and update the UI. Have you seen that? Every time you change the state, you need to update the UI.
So what? Well, let’s see how we can implement it with out the help of any framework:
这种形式的状态基本上可以是一个包含电子邮件地址和唯一标识符的对象数组。最初它将是空的。当通过按 enter键添加电子邮件地址时,将电子邮件地址添加到数组中并更新 UI。当用户点击“delete”时,你删除了电子邮件地址并更新了 UI。你们看到了吗? 每次更改状态时,都需要更新UI。
那又怎样?好吧,让我们看看如何在没有任何框架的帮助下实现它:
A vanilla implementation of a somewhat complex UI
A vanilla JS implementation of this UI
The code illustrates very well the amount of work needed to make a somewhat complex UI with vanilla JavaScript (using classic libraries such as jQuery would have been similar).
In the example the static structure is created in the HTML, whereas the dynamic stuff is created in JavaScript (with document.createElement). This is the first problem: the JavaScript code that builds the UI is not very readable and we are defining the UI in two different parts. We could have used innerHTML and it could have been more readable but it’s less efficient and can cause Cross Site Scripting bugs. We could have used a templating engine as well, but if you regenerate a big subtree of the DOM you have two problems: is not very efficient and you have to usually reconnect the event handlers.
But that’s not the biggest problem. The biggest problem is always updating the UI on every state change. Every time the state is updated there is a lot of code required to update the UI. When adding an email address in the example it takes 2 lines of code to update the state, but 13 lines to update the UI. And we made the UI as simple as possible!!
代码很好地说明了使用原生 JavaScript (使用 jQuery 这类的经典库也可以)制作稍微复杂的UI所需的工作量。
在这个例子中,静态结构是在 HTML 中创建的,而动态内容是在 JavaScript 中创建的(使用 document.createElement )。这是第一个问题: 构建 UI 的 JavaScript 代码可读性不强,我们将 UI 定义为两个不同的部分。我们可以使用 innerHTML,它可以更易读,但它的效率较低,并可能导致跨站点脚本错误。我们也可以使用模板引擎,但如果重新生成 DOM 的大子树,就会有两个问题: 效率不高,而且通常需要重新连接事件处理函数。
但这还不是最大的问题。最大的问题是在每次状态改变时都要更新 UI。每次更新状态时,都需要大量代码来更新 UI。在本例中添加电子邮件地址时,更新状态需要2行代码,更新 UI 需要13行代码。即使我们让 UI 尽可能的简单!
It is not only hard to write and hard to reason about, but more importantly: it is also extremely fragile. Imagine we need to implement the ability to sync the list with a server. We will need to compare our data with the data received from the server. This involves comparing all the identifiers and the content (we could have in memory a copy of a record with the the same identifier but with different data).
We will need to implement a lot of ad-hoc presentation code to mutate the DOM efficiently. And if we make any minimal mistake, the UI will be out of sync from the data: missing information, showing wrong information, or completely messed up with elements not responding to the user or even worse, triggering wrong actions (e.g. clicking on a delete button deletes the wrong item).
So, maintaining the UI in sync with the data involves writing a lot of tedious, fragile, and fragile code.
它不仅难以书写,难以推理,更重要的是: 它也是极其脆弱的。
假设我们需要实现将列表与服务器同步的功能。我们需要将我们的数据与从服务器接收到的数据进行比较。这涉及到比较所有标识符和内容(我们可以在内存中保存具有相同标识符但数据不同的记录副本)。
我们需要实现许多特殊代码来有效地改变DOM。如果我们犯了一点小错误,UI 就会与数据不同步: 丢失信息,显示错误信息,或者元素完全混乱,不能响应用户,甚至更糟,触发错误的操作(例如,点击删除按钮删除错误的项目)。
因此,维护 UI 与数据同步需要编写大量冗长、脆弱和脆弱的代码。
Declarative UIs to the rescue
So it is not the community, it is not the tools, nor the ecosystem, nor the third-party libraries,…
The biggest, by far, improvement these frameworks provide is having the ability to implement UIs that are guaranteed to be in sync with the internal state of the application
Well, almost. It is true if you don’t mess with some rules that each particular framework might have (e.g. immutability of the state).
We define the UI in a single shot, not having to write particular UI code in every action, and we always get the same output due to a particular state: the framework automatically updates it after the state changes.
所以它不是社区,不是工具,也不是生态系统,也不是第三方库……
到目前为止,这些框架提供的最大改进是能够实现保证与应用程序内部状态同步的 UI
嗯, 基本上。如果你不打乱每个特定框架可能有的规则(例如状态的不变性),这是正确的。
我们一次性地定义了UI,不需要在每个操作中都写特定的UI代码,并且由于特定的状态,我们总是会得到相同的输出: 在状态改变后,框架会自动更新它。
How does it work?
There are two basic strategies:
Re-render the whole component: React. When the state of a component changes it renders a DOM in memory and compares it with the existing DOM. Actually since that would be very expensive it renders a Virtual DOM and compares it with the previous Virtual DOM snapshot. Then it calculates the changes and performs the same changes to the real DOM. This process is called reconciliation.
Watch for changes using observers: Angular and Vue.js. Your state variables are observed and when they change only the DOM elements where those values are/were involved in the rendering are updated.
有两种基本策略:
-
重新渲染整个组件: React。当组件的状态发生变化时,它会在内存中构造一个 DOM,并将其与现有的DOM进行比较。实际上,由于操作 DOM 是非常昂贵的,所以它将呈现一个虚拟 DOM 并将其与前一个虚拟 DOM 快照进行比较。然后计算更改,并对实际 DOM 执行相同的更改。这个过程叫做
reconciliation
。 -
使用 observer 观察变化: Angular 和 Vue.js。您的状态变量将被观察,当它们发生变化时,只更新那些值所在的 DOM 元素。
What about web components?
Many times people compare React, Angular and Vue.js with web components. This is a clear indicator that many people do not understand the biggest benefit these frameworks provide: keeping the UI in sync with the state. And web components doesn’t provide anything for that. They just provide a