现代 JavaScript 框架存在的主要原因

简评:现代 JavaScript 框架的出现最主要是解决哪个问题?这篇文章很好的解释了这个问题。

我见过许多人盲目地使用像 React,Angular 或 Vue.js 这样的现代框架。这些框架提供了许多有趣的东西,通常人们会忽略这些框架存在最主要的原因,这些原因不是

  • 它们基于组件;
  • 它们有一个强大的社区;
  • 它们有很多第三方库;
  • 它们有很多有用的第三方组件;
  • 它们有浏览器插件,可以帮助调试;
  • 它们适用于单页面应用程序。

这些都不是最本质的原因,最本质的原因是保持 UI 和状态同步并不容易。

UI 和 状态同步难在哪?

假如,您正在构建一个 Web 应用程序,用户可以填写他人的 email 地址来发起邀请。并且邀请列表有两种状态:

  1. 空状态,我们在这个状态下提示用户填写邮箱。
  2. 非空状态,这种状态我们需要列出出等待被邀请的用户,并且提供删除按钮。

尝试使用纯 JavaScript 实现这种功能

源码和效果可以到参考:codepen

index.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)

这段代码很好地说明了使用纯 JavaScript 实现一个有点小复杂的 UI 所需要的工作量。

在示例中,静态结构在 HTML 中创建,动态内容使用 JavaScript 来创建。这种方式有几个问题:

构建 UI 的 JavaScript 代码可读性不高,我们用两个不同的部分来定义 UI。我们可以使用 innerHTML来让代码更容读,但是这样效率不高,而且容易引发跨站脚本漏洞。我们也可以使用模板引擎,但是如果重新生成大的 DOM 的子节点又会遇到两个问题:效率不高,通常需要重新连接 event handler。

但这都是小问题,最主要的问题是:我们需要在状态变更的时候更新 UI。每一次状态出现变更我们都需要使用大量的代码来更新 UI。上面的例子我们更新状态是用了两行的代码,但是更新 UI 却耗费了 13 行代码(尽管这个 UI 并不复杂)。

它不仅编写起来复杂而且还很脆弱。想象一下,我们需要实现将列表于服务器同步的功能。我们需要将本地数据和服务器发来的数据进行比较。并且需要点对点的对每个变更同步到 DOM 节点中。如果这个过程中有每一步出现差错都直接导致 UI 同步失败。

因此,维护 UI 与数据同步需要编写大量繁琐,脆弱和脆弱的代码。

声明式 UI 解决方案

它是不是社区,它不是工具,也不是生态系统,也不是第三方库......

到目前为止,这些框架提供的最大的改进是实现应用状态和 UI 同步。

我们只需要定义一次 UI,不必编写为每一次动作编写 UI。相同的状态总能得到相同的 UI 输出(状态和 UI 同步,状态变更后会自动更新 UI)。

原理

有两个基本策略:

  • 重新渲染整个组件: React。当组件的状态发送变化时,它会在内存中渲染一个 DOM,并和现有 DOM 进行比较。但是为了降低成本,实际上它会渲染一个虚拟 DOM,来和之前的虚拟 DOM 进行比较,然后计算更改并对真实 DOM 进行修改。
  • 使用观察者来监听变化: Angular 和 Vue.js 观察你的状态变化,并且只会更新关联的 DOM 元素。

和 Web Component 比较?

很多时候人们将 React,Angular 和 Vue.js 和 Web 组件进行比较。很多人不理解这些框架提供的最大好处:保持 UI 和状态同步。而 Web 组件并不提供内容,它是一套规范,以便开发者可以自由创建可重用的元素。所以单纯使用 Web Component + 纯 JavaScript 仍然需要手动保证状态同步,要实现高效易维护的 UI 还需要使用 现代 JavaScript 框架。

自己实现

自己实现一个类似的功能,能够加深对原理的理解。我们尝试使用 虚拟DOM (而不是直接用第三方框架)实现一个类似 React 的框架,来重写刚刚的 demo。

下面是我们 Framework 的核心部分,代表所有组件的基类:

下面是基于 Component 重写的邮箱邀请的应用(借助 babel 变换来支持 JSX)这里是源码

现在的 UI 是声明性的,而且我们没有直接使用任何框架。我们可以实现以任何方式更改状态的逻辑,并且不需要额外编写 UI 同步的代码。


原文链接:The deepest reason why modern JavaScript frameworks exist
推荐阅读:Level UP! 提升你的编程技能

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,928评论 25 707
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_X自主阅读 15,975评论 3 119
  • 尊敬的各位领导,各位来宾。现在我们已经来到了国家文物保护单位,国家4a级景区关西新围,我们现在所看到的入口大门,大...
    月清浅阅读 627评论 0 0
  • 和妹妹看完战狼2步行在雨后的小区里,妹妹美美的穿了裙子配了一双布料黑色的小高跟鞋小心翼翼的绕过每个积水的小水坑。迎...
    海秋花阅读 143评论 0 0
  • [原创首发于个人微信公众号,特此声明。] 没有纯粹的真诚,也没有纯粹的套路,这就是我眼中的职场。 你心中有佛,处处...
    终身学习的细嗅蔷薇阅读 251评论 0 1