原文地址。本文從屬於筆者的Web 前端入門與最佳實踐。前端
記得上次面試的時候,有人問我怎麼看待全棧開發這個概念,筆者一直以爲,對於小團隊與較簡單的業務邏輯,全棧能夠極大地提升產品開發效率。可是所謂磨刀不誤砍柴工,隨着對性能、清晰可維護的代碼架構的需求日漸提高,相似於Meteor這樣所謂的Isomorphic全棧架構反而成了一種阻礙,大大增長整個產品架構的複雜度。其中一個核心的Issue就是在於當你將先後端的狀態無差異的處理,而不進行任何分割的時候,你來自於Domain/DataBase/Server/UI的狀態迅猛增加,最終將你的代碼變成一團亂麻。而做爲全棧開發者,應該如何應對這種複雜性的陡升呢?仍是須要在所謂的客戶端與服務端之間劃分一個明確的狀態界限,而且以API Provider與Consumer的方式將客戶端與服務端進行解耦,這也符合SOLID原則,每一個系統內部應該儘可能少的瞭解外部系統的細節。git
一言以蔽之,狀態就是你應用中流動的數據。在工程師們提出了State
這個概念以後,每一個人都對它有本身獨特的理解,特別是隨着JavaScript富客戶端應用的爆炸式增加該詞也被賦予了各式各樣的含義,讓人們無所適從。State是對於你應用當前的屬性、配置或者一些定量特徵的總結。具體而言,譬如用戶的提示語言,遊戲中的計時器或者某個組件的可見性。另外一方面,狀態也表明着服務端的緩存、或者不一樣用戶存放於數據庫中的數據,這也是一種狀態。github
實際上,任何應用中都不會只存在某種單一的狀態,狀態以不一樣的形式出如今應用的不一樣層級,咱們首先要作的就是搞清楚應該如何區分這些狀態,而且應該如何因地制宜地處理這些狀態。面試
Domain State便是你應用服務端的狀態,也就是你應用面向的某個特定領域的狀態。譬如咱們正在爲Grocery Store開發的Web應用,能夠預見的,咱們會在應用中發現以下通用狀態:認證、校驗、錯誤處理等等,統一的咱們也會發現不少與超級市場這一產業相關的狀態。這就是所謂的領域特定業務邏輯(Domain Specific Business Logic),這些狀態會應用於行業相關的業務邏輯代碼。Domain State來自於服務端,而且須要根據用戶的Session進行持久化存儲,以便於更好地與客戶端進行交互。這裏以GraphQL爲例展現一個簡單的Domain State查詢(若是你還不瞭解GraphQL,能夠參考GraphQL初探:從REST到GraphQL,更完善的數據查詢定義):數據庫
// Lets say we want to know the state of our friends list at any given time // Lets make a GraphQL query to represent this: user(id: "1") { name friends { name } }
在GraphQL中咱們編寫所謂的queries
來獲取Domain State,如上面的請求中咱們會返回編號爲1的用戶的姓名與朋友信息,返回結果以下所示:segmentfault
{ "data": { "user": { "name": "Abhi Aiyer", "friends": [ { "name": "Ben Strahan" }, { "name": "Sashko Stubailo" }] } } }
若是咱們須要獲取更多的領域信息:後端
jobs(id: "32hkrv32ZKjd3jlwzhk") { description, position, wage { max }, managers { name, email }, status, published }
這裏咱們從job表中但願獲取更多關聯信息,返回結果大概以下:緩存
{ "data": { "jobs": { "description": "Write cool blog posts", "position": "Programmer", "wage": { "max": 24 }, "managers": [ { "name": "Larry", "email": "larry@stooges.com" }, { "name": "Moe", "email": "moe@stooges.com" } ], "status": "PUBLISHED", "publishedAt": 1460294879 } } }
Domain State大概包含了你須要管理的核心狀態中的差很少一半部分,Browser表明着另外一半,而且有它本身的職責與能力。儘管如今UI開發中無狀態組件的概念很流行,Browser主要負責存放用戶剛剛輸入的或者配置的狀態信息。除此以外,Browsers還會緩存頁面、設置Cookie、在LocalStorage中設置Token、載入CSS等等。除了Web Browsers以外,咱們的客戶端應用還有專門的狀態管理工具。若是你是使用React進行界面開發而且選用了單向數據流架構,你大概會選用Flux庫或者某個變種。Flux系列框架可以幫助前端開發人員管理客戶端的狀態,譬如某個組件的可見性控制、用戶輸入的獲取或者響應,或者根據不一樣的用戶尺寸展現不一樣的尺寸等等。這裏以Redux的reducers爲例:架構
function visibilityOfButton(state = false, action = {}) { switch (action.type) { case "TOGGLE_VISIBILITY": // return opposite of the current state return !state; default: return state; } } function inputFromUser(state = {}, action) { switch (action.type) { case "UPDATE_DATA": // return a new object that has data from the action return { ...state, ...action.data } default: return state; } }
在Redux中,reducers闡述了UI狀態的變化之路,你能夠看到當前的狀態以及根據不一樣的Action會進行怎樣的狀態變化。譬如根據上面的reducers,咱們的狀態樹大概是這個樣子的:框架
{ visibilityOfButton: false, inputFromUser: {} }
當用戶點擊按鈕以後,產生的Action以及對應的State變動以下所示:
// Redux utilizes a command like pattern. // Our Store represents the receiver here // the dispatch represents the executor // the object passed to the dispathcer is the command Store.dispatch({ type: "TOGGLE_VISIBILITY" }); 'REVIOUS STATE: { visibilityOfButton: false, inputFromUser: {} }' 'ACTION -> type: "TOGGLE_VISIBILITY"' 'NEXT STATE: { visibilityOfButton: true, inputFromUser: {} }'
在JavaScript中,純粹的同構的先後端是個很是酷的概念,不過就如筆者在文首所講,在CS架構中仍是保持客戶端與服務端的某個邊界仍是頗有意義的。而在實踐中,這個邊界每每就是所謂的Public API。從前端開發者的角度而言,對於服務端我只須要瞭解API相關的內容,天然也但願API可以穩定的保證一種向後兼容性,不向後兼容的API會強制前端開發者及時地作出響應。通常來講,客戶端須要維護兩種狀態,用戶的交互以及須要持久化存儲到某個域中的結果。所以,咱們但願可以以一種清晰可預測的方式來管理咱們的狀態。以API的方式分隔客戶端狀態與服務端狀態便是爲了保證所寫代碼的職責分離,你確定不但願模塊對於外部系統有太多的依賴。
在具體的實現API的時候,咱們的目標兼括了穩定性與性能自己,即便你本身做爲全棧開發者同時負責了先後端的代碼,我也是不建議在開發服務端業務邏輯的代碼時過多的考慮到UI State。我使用Redux來管理應用前端的UI狀態,它提倡的模式與文檔支持都很是好,社區自己也很是地活躍。GraphQL也是值得推薦的用於分割狀態的工具,在GraphQL中,客戶端可以以查詢地方式從服務端請求狀態,GraphQL能夠將多個Endpoint的狀態合併返回給前端,從而使前端不須要去關心後端究竟是如何實現的。
綜上所述,隨着應用的日漸增加,狀態管理的複雜度也與日俱增。而可以掌控這種複雜的情形,也是軟件工程師的必備能力之一。