「譯」現代框架存在的根本原因

前言

我曾見過許多人盲目地使用像 React,Angular 或 Vue 這樣的現代框架。這些框架提供了許多有趣的東西,但通常人們會忽略它們存在的根本原因。

並不是我們所想的以下原因:

  1. 它們基於組件;
  2. 它們有強大的社區;
  3. 它們有很多第三方庫來解決問題;
  4. 它們有很多第三方組件;
  5. 它們有瀏覽器擴展工具來幫助調試;
  6. 它們適合做單頁應用。
「譯」現代框架存在的根本原因

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

UI 與狀態同步非常困難

為什麼

假設你在開發一個這樣需求:

用戶可以通過發送郵件來邀請其他用戶。

UI 交互設計如下:

  1. 輸入框有一個空狀態(帶有提示信息)
  2. 輸入郵箱後展示相應的 郵箱,每個地址的右側都有一個刪除按鈕。

原型如下:

「譯」現代框架存在的根本原因

這個表單是一個包含電子郵件地址和唯一標識符的對象數組。最初它將是空的。輸入郵件回車後,向該數組中添加一項並更新 UI。當用戶點擊刪除時,刪除對應的項並更新 UI。

感受到了嗎?每次更改狀態時,都需要更新 UI。

我聽到你再說,那又怎樣?OK,讓我們看看如何在不用框架的情況下實現它。

原生實現相對複雜的 UI

// 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 通過 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 與狀態同步,則需要開發者手工完成,或者使用相關庫。/<template>

自己開發一個框架?

如果熱衷於瞭解底層原理,想知道虛擬 DOM 的具體實現。那,為何不試著在不使用框架的情況下,僅使用虛擬 DOM 來重寫原生 UI呢?

這裡是框架的核心,所有組件的基礎類。

我喜歡學習事物的原理 —— 虛擬 DOM 實現。那麼,為什麼我們學習 Virtual DOM 的實現呢?

這是框架的核心,是任何組件的基類。

「譯」現代框架存在的根本原因

這裡是重寫後的 AddressList 組件(使用 babel 來支持 JSX )。

「譯」現代框架存在的根本原因

現在 UI 是聲明式的,沒有使用任何框架。我們添加新邏輯來改變狀態的同時,不再需要編寫額外的代碼來保持 UI 同步。

結論

  1. 現代 JavaScript 框架解決的主要問題是保持 UI 與狀態同步。
  2. 使用原生 JavaScript 編寫複雜、高效而又易於維護的 UI 界面幾乎是不可能的。
  3. Web components 並沒有提供解決 UI 與狀態同步的方案。
  4. 使用現有的虛擬 DOM 庫去開發自己的框架並不困難,但不建議。
作者:小生方勤
鏈接:https://juejin.im/post/5d1df53fe51d4510a5033627
來源:掘金 著作權歸作者所有。


分享到:


相關文章: