原文連接:medium.com/dailyjs/the…javascript
我曾見過許多人盲目地使用像 React
,Angular
或 Vue
這樣的現代框架。這些框架提供了許多有趣的東西,但一般人們會忽略它們存在的根本緣由。html
並非咱們所想的如下緣由:前端
最基本的、最根本的、最深入的緣由是:java
UI 與狀態同步很是困難數組
假設你在開發一個這樣需求:瀏覽器
用戶能夠經過發送郵件來邀請其餘用戶。bash
UI 交互設計以下:服務器
原型以下: babel
這個表單是一個包含電子郵件地址和惟一標識符的對象數組。最初它將是空的。輸入郵件回車後,向該數組中添加一項並更新 UI。當用戶點擊刪除時,刪除對應的項並更新 UI。app
感覺到了嗎?每次更改狀態時,都須要更新 UI。
我聽到你再說,那又怎樣?OK,讓咱們看看如何在不用框架的狀況下實現它。
// 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);
複製代碼
codepen 地址:codepen.io/gimenete/em…
以上代碼很好地說明了使用原生 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.
有兩個基本的策略:
從新渲染整個組件,如 React。當組件中的狀態發生改變時,在內存中計算出新的 DOM 結構後與已有的 DOM 結構進行對比。實際上,這是很是昂貴的。於是採起虛擬 DOM ,經過對比狀態變化先後虛擬 DOM 的不一樣,計算出變化後再改變真實 DOM 結構。這個過程稱爲調和(reconciliation)。
經過觀察者監測變化,如 Angular 和 Vue。應用中狀態的屬性會被監測,當它們發生變化時,相應的 DOM 元素會從新渲染。
不少狀況,人們會把 React、 Angular 和 Vue 與 Web components 進行對比。這些人顯然不理解這些框架所提供的最大好處:保持 UI 與狀態同步。
Web components 並不提供這種同步機制。它只是提供了一個<template>
標籤。若是你在應用中使用 Web components 時,想保持 UI 與狀態同步,則須要開發者手工完成,或者使用相關庫。
若是熱衷於瞭解底層原理,想知道虛擬 DOM 的具體實現。那,爲什麼不試着在不使用框架的狀況下,僅使用虛擬 DOM 來重寫原生 UI呢?
這裏是框架的核心,全部組件的基礎類。
我喜歡學習事物的原理 —— 虛擬 DOM 實現。那麼,爲何咱們學習 Virtual DOM 的實現呢?
這是框架的核心,是任何組件的基類。
這裏是重寫後的 AddressList 組件(使用 babel 來支持 JSX )。
如今 UI 是聲明式的,沒有使用任何框架。咱們添加新邏輯來改變狀態的同時,再也不須要編寫額外的代碼來保持 UI 同步。
若是你想進【大前端交流羣】,關注公衆號點擊「交流加羣」添加機器人自動拉你入羣。關注我第一時間接收最新干貨。