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

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

  • 它們基於組件;它們有一個強大的社區;它們有很多第三方庫;它們有很多有用的第三方組件;它們有瀏覽器插件,可以幫助調試;它們適用於單頁面應用程序。
現代 JavaScript 框架存在的主要原因

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

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

UI 和 狀態同步難在哪?

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

  1. 空狀態,我們在這個狀態下提示用戶填寫郵箱。非空狀態,這種狀態我們需要列出出等待被邀請的用戶,並且提供刪除按鈕。
現代 JavaScript 框架存在的主要原因

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

源碼和效果可以到參考:codepen。

index.html 代碼

Type an email address and hit enter

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 並不複雜)。

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

它不僅編寫起來複雜而且還很脆弱。想象一下,我們需要實現將列表於服務器同步的功能。我們需要將本地數據和服務器發來的數據進行比較。並且需要點對點的對每個變更同步到 DOM 節點中。如果這個過程中有每一步出現差錯都直接導致 UI 同步失敗。

因此,維護 UI 與數據同步需要編寫大量繁瑣,脆弱和脆弱的代碼。

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

它是不是社區,它不是工具,也不是生態系統,也不是第三方庫......

到目前為止,這些框架提供的最大的改進是實現應用狀態和 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 的核心部分,代表所有組件的基類:

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

下面是基於 Component 重寫的郵箱邀請的應用(藉助 babel 變換來支持 JSX)這裡是源碼:

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

現在的 UI 是聲明性的,而且我們沒有直接使用任何框架。我們可以實現以任何方式更改狀態的邏輯,並且不需要額外編寫 UI 同步的代碼。

原文:The deepest reason why modern JavaScript frameworks exist


分享到:


相關文章: