[译]现代框架存在的根本原因
前言
我曾见过许多人盲目地使用像 React
,Angular
或 Vue
这样的现代框架。这些框架提供了许多有趣的东西,但通常人们会忽略它们存在的根本原因。
并不是我们所想的以下原因:
- 它们基于组件;
- 它们有强大的社区;
- 它们有很多第三方库来解决问题;
- 它们有很多第三方组件;
- 它们有浏览器扩展工具来帮助调试;
- 它们适合做单页应用。
![[译]现代框架存在的根本原因](https://static.blogweb.cn/article/b893ca214f6c467bbab93ffc4b8a1621.webp)
最基本的、最根本的、最深刻的原因是:
UI 与状态同步非常困难
为什么
假设你在开发一个这样需求:
用户可以通过发送邮件来邀请其他用户。
UI 交互设计如下:
- 输入框有一个空状态(带有提示信息)
- 输入邮箱后展示相应的 邮箱,每个地址的右侧都有一个删除按钮。
原型如下:
![[译]现代框架存在的根本原因](https://static.blogweb.cn/article/31844211757a4261b5c4a5d8441134a7.webp)
这个表单是一个包含电子邮件地址和唯一标识符的对象数组。最初它将是空的。输入邮件回车后,向该数组中添加一项并更新 UI。当用户点击删除时,删除对应的项并更新 UI。
感受到了吗?每次更改状态时,都需要更新 UI。
我听到你再说,那又怎样?OK,让我们看看如何在不用框架的情况下实现它。
原生实现相对复杂的 UI
// html
<html>
<body>
<div id="addressList">
<form>
<input>
<p class="help">Type an email address and hit enter</p>
<ul>
</ul>
</form>
</div>
</body>
</html>
// js
class AddressList {
constructor(root) {
// state variables
this.state = []
// UI variables
this.root = root
this.form = root.querySelector('form')
this.input = this.form.querySelector('input')
this.help = this.form.querySelector('.help')
this.ul = root.querySelector('ul')
this.items = {} // id -> li element
// event handlers
this.form.addEventListener('submit', e => {
e.preventDefault()
const address = this.input.value
this.input.value = ''
this.addAddress(address)
})
this.ul.addEventListener('click', e => {
const id = e.target.getAttribute('data-delete-id')
if (!id) return // user clicked in something else
this.removeAddress(id)
})
}
addAddress(address) {
// state logic
const id = String(Date.now())
this.state = this.state.concat({ address, id })
// UI logic
this.updateHelp()
const li = document.createElement('li')
const span = document.createElement('span')
const del = document.createElement('a')
span.innerText = address
del.innerText = 'delete'
del.setAttribute('data-delete-id', id)
this.ul.appendChild(li)
li.appendChild(del)
li.appendChild(span)
this.items[id] = li
}
removeAddress(id) {
// state logic
this.state = this.state.filter(item => item.id !== id)
// UI logic
this.updateHelp()
const li = this.items[id]
this.ul.removeChild(li)
}
// utility method
updateHelp() {
if (this.state.length > 0) {
this.help.classList.add('hidden')
} else {
this.help.classList.remove('hidden')
}
}
}
const root = document.getElementById('addressList')
new AddressList(root);
codepen 地址:codepen.io/gimenete/em…
以上代码很好地说明了使用原生 JavaScript 实现一个相对复杂的 UI 所需的工作量。
在这个例子中,HTML
负责创建静态页面,JavaScript
通过 document.createElement
改变 DOM
结构。
这引来了第一个问题:
构建 UI 相关的 JavaScript 代码比较复杂,而且 UI 构建分为了两部分。我们本可以用 innerHTML,虽然它有更高的可读性,但降低了页面的性能,同时可能存在 CSRF 漏洞。
我们也可以使用模板引擎,但如果是大面积地修改 DOM,会面临两个问题:效率不高与需要重新绑定事件处理器。
但这不是最大问题。最大的问题是每当状态发生改变时都要手动更新 UI。每次状态更新时,都需要很多代码来改变 UI。当添加电子邮件地址时,只需要两行代码来更新状态,但要十三行代码更新 UI。而且我们已经让 UI 尽可能简单了!
![[译]现代框架存在的根本原因](https://static.blogweb.cn/article/a977664cd2204a4a8b14f866f360318c.webp)
它不仅难以编写而且难以推理,更重要的是:它也非常脆弱。
假设我们我们需要实现将列表与服务器同步的功能,我们需要将数据同服务器返回的数据作对比。
我们需要写大量代码,使 DOM 更新更加高效。但如果有任何微小的错误,视图将与数据不再同步。
因此,为了保持视图与状态同步,我们需要写大量乏味且脆弱的代码。
响应式拯救一切
之所以使用框架不是因为社区,不是因为工具,不是因为生态,不是因为第三方库......
目前为止,框架最大的改进是保证 UI 和数据同步。
只要你清楚框架的使用规则,就可以很愉快的使用他们。
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.
框架是如何工作的呢?
有两个基本的策略:
-
重新渲染整个组件,如 React。当组件中的状态发生改变时,在内存中计算出新的 DOM 结构后与已有的 DOM 结构进行对比。实际上,这是非常昂贵的。因而采取虚拟 DOM ,通过对比状态变化前后虚拟 DOM 的不同,计算出变化后再改变真实 DOM 结构。这个过程称为调和(reconciliation)。
-
通过观察者监测变化,如 Angular 和 Vue。应用中状态的属性会被监测,当它们发生变化时,相应的 DOM 元素会重新渲染。
Web components 怎么样
很多情况,人们会把 React、 Angular 和 Vue 与 Web components 进行对比。这些人显然不理解这些框架所提供的最大好处:保持 UI 与状态同步。
Web components 并不提供这种同步机制。它只是提供了一个<template>
标签。如果你在应用中使用 Web components 时,想保持 UI 与状态同步,则需要开发者手工完成,或者使用相关库。
自己开发一个框架?
如果热衷于了解底层原理,想知道虚拟 DOM 的具体实现。那,为何不试着在不使用框架的情况下,仅使用虚拟 DOM 来重写原生 UI呢?
这里是框架的核心,所有组件的基础类。
我喜欢学习事物的原理 —— 虚拟 DOM 实现。那么,为什么我们学习 Virtual DOM 的实现呢?
这是框架的核心,是任何组件的基类。
![[译]现代框架存在的根本原因](https://static.blogweb.cn/article/39df469f2bed453f98966c359ec36555.webp)
这里是重写后的 AddressList 组件(使用 babel 来支持 JSX )。
![[译]现代框架存在的根本原因](https://static.blogweb.cn/article/d53a6e9261ef4ad2977d35905b3831a2.webp)
现在 UI 是声明式的,没有使用任何框架。我们添加新逻辑来改变状态的同时,不再需要编写额外的代码来保持 UI 同步。
结论
- 现代 JavaScript 框架解决的主要问题是保持 UI 与状态同步。
- 使用原生 JavaScript 编写复杂、高效而又易于维护的 UI 界面几乎是不可能的。
- Web components 并没有提供解决 UI 与状态同步的方案。
- 使用现有的虚拟 DOM 库去开发自己的框架并不困难,但不建议。
最后
如果你想进【大前端交流群】,关注公众号点击“交流加群”添加机器人自动拉你入群。关注我第一时间接收最新干货。
![[译]现代框架存在的根本原因](https://static.blogweb.cn/article/7f2c03d21080422ebc9d37f67d50aaeb.webp)
转载自:https://juejin.cn/post/6844903882167074823