您當前的位置:首頁 > 體育

淺談現代前端框架技術思想

作者:由 繁星 發表于 體育時間:2019-05-14

本文淺談一下前端技術的發展背後的動機、解決問題的思路和借鑑的一些思想等,更適合有使用過 React 或 Vue 等現代前端框架的開發者閱讀。如果你沒接觸過這些技術,本文也可以幫你擴充套件一下視野,提升思維高度。

抽象,本質上是一種泛化與概括的思維方式。

科學的發展其實就是人類不斷嘗試抽象和解釋世界的過程。基於已有的現象或資料去努力發現共性、規律,抽象出一個模型或者公式(定律定理),然後再驗證是否可以解釋所有滿足條件的現象。一個正確的科研成果基本都是在一定前提下可以被重複驗證和解釋一類現象的,該成果進而可以被加以利用到實際生產活動當中,促進生產效率提升和社會的發展。

在計算機科學中,抽象與分層是一種降低計算機系統複雜度的基本思想和有效方法。分層是建立在抽象的基礎上,分散關注、鬆散耦合、邏輯複用,在此基礎上可以產生行業標準的定義。

自從 Ajax 出現以來,前端終於可以自成體系的獨立開發部署了。前端透過 Ajax 請求獲取到資料後,可以有能力自行維護資料,並透過 DOM 操作來展示資料。處理使用者操作所觸發的 DOM 事件,對資料進行一定處理後與伺服器進行同步,同時更新區域性內容或藉助前端路由進行頁面跳轉,而不用重新整理頁面。自此前端有能力跟後端完全分離,也就是我們常說的 SPA。

淺談現代前端框架技術思想

概括來說,前端在此階段主要做的事情有這些:

控制頁面渲染

編寫模板和樣式

透過 DOM 操作渲染初始資料

處理 DOM 事件控制組件樣式

資料變更後透過 DOM 操作更新區域性頁面內容

維護本地記憶體中的資料

傳送請求與伺服器同步資料

資料加工適配和校驗

更新使用者操作時相對應的資料

這些事情同時做起來是非常繁瑣的,而我們所要開發的系統卻越來越複雜,導致這種開發模式變得越加困難。擁有所有底層能力,意味著前端開發者寫程式碼時可以為所欲為。而前端入門門檻低,開發者的水平參差不齊,這給系統的穩定性和可維護性帶來了巨大挑戰。同時,需求與生產力越發的不匹配,變革的訴求越來越強。

前端技術在 2010 年前後發展迅猛,2013 年 React。js 誕生提出了基於單向資料流的 Flux 架構後,前端技術大方向基本定型,基本可以產生行業標準了。接下來的技術發展基本是在原有方向上不斷的最佳化和完善,提升效能,改善開發體驗。

現代前端框架的發展過程本質還是抽象與分層的基本思想的落地:有一些重複或相似的工作是可以抽象出來做針對性處理的,最好能夠讓我們可以不再操心。就像 Java 的 Garbage Collection 一樣,GC 關注於記憶體回收並不斷完善和提升效能,開發者不用再關注記憶體管理這一層,同時也能降低記憶體洩露風險。抽象來說,前端所做的事情有兩個,而現代前端框架技術也是圍繞這兩點不斷進化的:

將資料和變化後的資料渲染成UI,保持同步;

維護本地資料並和伺服器資料保持同步;

現代前端框架將頁面渲染的能力抽象和封裝出來

如今前端新人已經很少接觸 DOM 程式設計了,甚至對瀏覽器原生事件的瞭解都不夠深入。這是因為現代框架將頁面渲染能力抽象和封裝起來,開發者只需要按照文件配置好模板即可,現代框架會自行將初始資料渲染成頁面。如果資料有變更,現代框架也提供能力渲染頁面保持與資料的同步:React 使用者只需呼叫 setState 方法,而 Vue 和 Angular 的使用者什麼都不用做,框架內部機制可以檢測到變更而自動更新頁面。

將頁面渲染能力封裝出來以後,就可以關注於這一塊不斷進行最佳化。一方面提升效能:虛擬 DOM 和 DIFF 演算法嘗試最大程度降低 DOM 操作從而提升渲染效能。另一方面改善開發體驗:元件化思想基於此得到廣泛認可和並不斷髮展,如今 React 提出的 Hooks 概念也是努力將管理狀態(資料)的元件與實現渲染邏輯的元件分開,降低巢狀元件的複雜度並提升複用性,也提供了更人性化的 API。

如今一個前端新人可以快速上手並基於開源元件庫低成本開發出一定功能的系統,都是得益於現代前端框架對頁面渲染能力的封裝。

現代前端框架提供了可選的狀態(資料)管理能力

之所以說是可選的,是因為你可能並不需要!這取決於你所面對系統的複雜度和現代框架所提供的狀態管理能力所帶來的優缺點。那麼現代前端框架狀態管理能力的設計借鑑了哪些思想呢?我們先來看看狀態機。

狀態機模型可以描述一切運轉中的事物。拿宇宙來說,如果我們有一個可以記錄宇宙所有狀態的相機,那麼按下一次快門所記錄的就是宇宙的一個 State,下一刻就是一個 Transition,宇宙的運轉就是一個無限狀態機,除非哪一刻宇宙毀滅了:

淺談現代前端框架技術思想

一個 SPA 也可以用狀態機模型來描述:State 代表了當前 SPA 本地記憶體中的資料 (由於頁面渲染能力已經被抽象和封裝了,這裡不再關注資料所單向對映的 UI ) ,而網路事件、瀏覽器事件、setTimeout 等和他們觸發的對本地記憶體中資料的更改可以抽象為 Transition。

淺談現代前端框架技術思想

現代前端框架提供的狀態管理能力其實是一個更關注“操作”的狀態機模型,但是實現的手段借鑑了 Event Sourcing 和 CQRS 。首先介紹一下Event Sourcing ,兩個要點:

關注每一次引發 Transition 的 Event,並做持久化;

State 是隻讀的,每一次 Transition 後都基於變更生成一個新的 State ,並與引發的 Event 做對映後做持久化(Immutable Data);

淺談現代前端框架技術思想

這種 Event First(Event-Driven vs Data-Driven) 的思想好處很多,對於前端而言最大的益處是 Snapshoot 的能力,Event 描述了 SPA 內所發生的一切事情,在目前越來越複雜的系統中遇到問題時可以溯源,重現錯誤,準確發現

State 在什麼時候,是由於什麼原因,如何變化的

,方便 debug 。同時,Event first 還給予我們很大的想象空間:如果有一個事件標準體系被前端和各種其他系統(比如 Log, Metric, BDI)實現了呢?是不是可以豐富資料採集,有利於大資料分析呢?

而CQRS又是什麼呢? Command Query Responsibility Segregation,命令查詢職責分離,一種讀寫分離的模式。以 Redux 為例:

淺談現代前端框架技術思想

Commands:

不返回任何結果,但更改一個物件或整個系統的狀態。在上圖中指 dispatch 一個 action 而呼叫的 reducer 對 store 的處理產生一個新的 store;

Query :

返回結果但不會改變物件的狀態,在上圖中指 subscribe store 以及註冊的 listener 裡呼叫 getState 方法;

拿 Redux 的示例 Shopping Cart Example 來說明一下,這裡只分析 Add to cart 一個 action,詳細程式碼請到 sandbox 裡查閱:

淺談現代前端框架技術思想

當用戶點選 Add to cart 按鈕時,對應 action 會 dispatch ADD_TO_CART,觸發 Products 和 Cart 裡對應的 reducer 去更改 state,即 command,操作過程是將 Products 裡對應 product 的庫存減 1,Cart 裡對應 product 數量加 1。

// products reducer

// products 裡對應 product 存貨數量減 1

const

products

=

state

action

=>

{

switch

action

type

{

case

ADD_TO_CART

return

{

。。。

state

inventory

state

inventory

-

1

}

default

return

state

}

}

// cart reducer

// 如果 cart 裡沒有對應的 product,新增新的 product

const

addedIds

=

state

=

initialState

addedIds

action

=>

{

switch

action

type

{

case

ADD_TO_CART

if

state

indexOf

action

productId

!==

-

1

{

return

state

}

return

。。。

state

action

productId

default

return

state

}

}

// cart 裡對應的 product 數量加 1

const

quantityById

=

state

=

initialState

quantityById

action

=>

{

switch

action

type

{

case

ADD_TO_CART

const

{

productId

}

=

action

return

{

。。。

state

productId

state

productId

||

0

+

1

}

default

return

state

}

}

reducer 更改了 Store 裡對應 state 後,會觸發變更通知 view 去獲取最新 state 並重新渲染,即 query。

const

getAddedIds

=

state

=>

fromCart

getAddedIds

state

cart

const

getQuantity

=

state

id

=>

fromCart

getQuantity

state

cart

id

const

getProduct

=

state

id

=>

fromProducts

getProduct

state

products

id

export

const

getTotal

=

state

=>

getAddedIds

state

reduce

((

total

id

=>

total

+

getProduct

state

id

)。

price

*

getQuantity

state

id

),

0

toFixed

2

export

const

getCartProducts

=

state

=>

getAddedIds

state

)。

map

id

=>

({

。。。

getProduct

state

id

),

quantity

getQuantity

state

id

}))

這樣最新的商品列表、購物車商品列表和總價都會渲染重新整理。此購物車例子比較簡單,再複雜點可以減少購物車商品數量,購物車某個商品全選等,而我們只需要編寫對應的 action 和 reducer 即可。從這個例子可以看出,對於前端而言,命令和查詢職責分離所帶來的優點主要有:

邏輯更加清晰,能夠看到哪些行為或者操作導致了 state 的變化;

處理狀態的方法更加原子化,提高複用性;

提供了一個標準的管理狀態的模式,保證程式碼輸出風格的一致性;

可擴充套件性強,合理新增 action 和對應的 reducer 即可;

關注於狀態管理後,可以做針對性的最佳化,提高效能;

完美結合 Eventing Source 模式,記錄下所有的事件,必要時用來對系統進行回退或者重放;

為不同層級的元件提供良好的獲取共享 State 的能力;

但是這些優點是需要付出代價的:

你需要寫更多的程式碼,對於簡單系統而言會降低開發效率!

,所以要根據系統複雜度酌情選擇使用。另外,巢狀層級深的元件之間獲取共享 State 也有其他方案可選。

總結:現代前端框架技術的發展正是抽象和分層思想的實踐。將前端問題分為資料狀態管理和與資料狀態保持同步的頁面渲染能力封裝。關注分離、鬆散耦合便可以分別針對性的最佳化,改善開發體驗。封裝好解決方案後,前端開發者就可以忽略其底層實現細節,把精力放在業務邏輯上。

資料參考

Introduction to CQRS

本文首發於github: 淺談現代前端框架技術思想 · Issue #8 · rainjay/blog

標簽: STATE  前端  action  渲染  頁面