专栏首页前端词典[译]现代框架存在的根本原因

[译]现代框架存在的根本原因

前言

我曾见过许多人盲目地使用像 ReactAngularVue 这样的现代框架。这些框架提供了许多有趣的东西,但通常人们会忽略它们存在的根本原因。

并不是我们所想的以下原因:

  1. 它们基于组件;
  2. 它们有强大的社区;
  3. 它们有很多第三方库来解决问题;
  4. 它们有很多第三方组件;
  5. 它们有浏览器扩展工具来帮助调试;
  6. 它们适合做单页应用。

最基本的、最根本的、最深刻的原因是:

UI 与状态同步非常困难

为什么

假设你在开发一个这样需求:

用户可以通过发送邮件来邀请其他用户。

UI 交互设计如下:

  1. 输入框有一个空状态(带有提示信息)
  2. 输入邮箱后展示相应的 邮箱,每个地址的右侧都有一个删除按钮。

原型如下:

这个表单是一个包含电子邮件地址和唯一标识符的对象数组。最初它将是空的。输入邮件回车后,向该数组中添加一项并更新 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>
// jsclass 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 地址:https://codepen.io/gimenete/embed/vRZLrq

以上代码很好地说明了使用原生 JavaScript 实现一个相对复杂的 UI 所需的工作量。

在这个例子中, HTML 负责创建静态页面, JavaScript 通过 document.createElement 改变 DOM 结构。

这引来了第一个问题:

构建 UI 相关的 JavaScript 代码比较复杂,而且 UI 构建分为了两部分。我们本可以用 innerHTML,虽然它有更高的可读性,但降低了页面的性能,同时可能存在 CSRF 漏洞。

我们也可以使用模板引擎,但如果是大面积地修改 DOM,会面临两个问题:效率不高与需要重新绑定事件处理器。

但这不是最大问题。最大的问题是每当状态发生改变时都要手动更新 UI。每次状态更新时,都需要很多代码来改变 UI。当添加电子邮件地址时,只需要两行代码来更新状态,但要十三行代码更新 UI。而且我们已经让 UI 尽可能简单了!

它不仅难以编写而且难以推理,更重要的是:它也非常脆弱。

假设我们我们需要实现将列表与服务器同步的功能,我们需要将数据同服务器返回的数据作对比。

我们需要写大量代码,使 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.

框架是如何工作的呢?

有两个基本的策略:

1. 重新渲染整个组件,如 React。当组件中的状态发生改变时,在内存中计算出新的 DOM 结构后与已有的 DOM 结构进行对比。实际上,这是非常昂贵的。因而采取虚拟 DOM ,通过对比状态变化前后虚拟 DOM 的不同,计算出变化后再改变真实 DOM 结构。这个过程称为调和(reconciliation)。

2. 通过观察者监测变化,如 Angular 和 Vue。应用中状态的属性会被监测,当它们发生变化时,相应的 DOM 元素会重新渲染。

Web components 怎么样

很多情况,人们会把 React、 Angular 和 Vue 与 Web components 进行对比。这些人显然不理解这些框架所提供的最大好处:保持 UI 与状态同步。

Web components 并不提供这种同步机制。它只是提供了一个 <template> 标签。如果你在应用中使用 Web components 时,想保持 UI 与状态同步,则需要开发者手工完成,或者使用相关库。

自己开发一个框架?

如果热衷于了解底层原理,想知道虚拟 DOM 的具体实现。那,为何不试着在不使用框架的情况下,仅使用虚拟 DOM 来重写原生 UI呢?

这里是框架的核心,所有组件的基础类。

我喜欢学习事物的原理 —— 虚拟 DOM 实现。那么,为什么我们学习 Virtual DOM 的实现呢?

这是框架的核心,是任何组件的基类。

这里是重写后的 AddressList 组件(使用 babel 来支持 JSX )。

现在 UI 是声明式的,没有使用任何框架。我们添加新逻辑来改变状态的同时,不再需要编写额外的代码来保持 UI 同步。

结论

  1. 现代 JavaScript 框架解决的主要问题是保持 UI 与状态同步。
  2. 使用原生 JavaScript 编写复杂、高效而又易于维护的 UI 界面几乎是不可能的。
  3. Web components 并没有提供解决 UI 与状态同步的方案。
  4. 使用现有的虚拟 DOM 库去开发自己的框架并不困难,但不建议。

本文分享自微信公众号 - 小生方勤(XSFQ_HSD),作者:小生方勤

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【前端词典】实现 Canvas 下雪背景引发的性能思考

    代码已上传至 github 【https://github.com/wanqihua/koa-canvas】

    小生方勤
  • 从这 25 个方面优化你的前端项目

    本文作者结合几年阿里经验,从基础层设计和应用层设计两个大方面,以及版本管理、统一脚手架、灰度发布、前后端分离、Mock、多页和单页、浏览器兼容、登录系统设计(单...

    小生方勤
  • 无敌秘籍之 — JavaScript手写代码

    用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)。

    小生方勤
  • angular中,防止按钮的两次点击 原

           在我的项目中,用户点击按钮后,如果网页响应慢一点,用户常会再次点击一下。结果就触发了两次 click 操作。 如果是查询还好,但如果是post,p...

    申君健
  • cssjshtml vue中共享变量的使用

    葫芦
  • ionic4 解决键盘遮挡输入框的问题

    lilugirl
  • Django REST + React + Redux 上传文件

    fanzhh
  • Python 爬虫第二篇(urllib+BeautifulSoup)

    在前面一篇「Python 爬虫第一篇(urllib+regex)」 我们使用正则表达式来实现了网页输入的提取,但是网页内容的提取使用正则是比较麻烦的,今天介绍一...

    keinYe
  • “谈钱不伤感情” 论云计算服务平台成本

    成本问题,是近些年困扰很多云服务提供商们的一个非常棘手的困难,云平台的构建以及数据中心等基础设施的建设和完善在建设成本上具有很大的市场区分,很多厂商也能够从价格...

    静一
  • 直击Black Hat USA 2015(三)

      今年BlackHat第二天的议题涉及多个非常热门的领域,如机器学习、IoT安全、移动安全等。今天小安主要关注的是机器学习方面的议题,这些议题信息量非常大,也...

    安恒信息

扫码关注云+社区

领取腾讯云代金券