現代 JavaScript 框架存在的主要緣由

簡評:現代 JavaScript 框架的出現最主要是解決哪一個問題?這篇文章很好的解釋了這個問題。

我見過許多人盲目地使用像 React,Angular 或 Vue.js 這樣的現代框架。這些框架提供了許多有趣的東西,一般人們會忽略這些框架存在最主要的緣由,這些緣由不是:javascript

  • 它們基於組件;
  • 它們有一個強大的社區;
  • 它們有不少第三方庫;
  • 它們有不少有用的第三方組件;
  • 它們有瀏覽器插件,能夠幫助調試;
  • 它們適用於單頁面應用程序。

這些都不是最本質的緣由,最本質的緣由是保持 UI 和狀態同步並不容易。html

UI 和 狀態同步難在哪?java

假如,您正在構建一個 Web 應用程序,用戶能夠填寫他人的 email 地址來發起邀請。而且邀請列表有兩種狀態:git

  1. 空狀態,咱們在這個狀態下提示用戶填寫郵箱。
  2. 非空狀態,這種狀態咱們須要列出出等待被邀請的用戶,而且提供刪除按鈕。

嘗試使用純 JavaScript 實現這種功能github

源碼和效果能夠到參考: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 代碼babel

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 所須要的工做量。app

在示例中,靜態結構在 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
相關文章
相關標籤/搜索