本文主要講述三方面內容:html
在講設計思想前,先簡單講下Redux是什麼?咱們爲何要用Redux?前端
Redux是JavaScript狀態容器,能提供可預測化的狀態管理。數據庫
它認爲:redux
咱們先來看看「狀態容器」、「視圖與狀態一一對應」以及「一個對象」這三個概念的具體體現。後端
Store是Redux中的狀態容器,它裏面存儲着全部的狀態數據,每一個狀態都跟一個視圖一一對應。數組
Redux也規定,一個State對應一個View。只要State相同,View就相同,知道了State,就知道View是什麼樣,反之亦然。promise
好比,當前頁面分三種狀態:loading(加載中)、success(加載成功)或者error(加載失敗),那麼這三個就分別惟一對應着一種視圖。前端工程師
如今咱們對「狀態容器」以及「視圖與狀態一一對應」有所瞭解了,那麼Redux是怎麼實現可預測化的呢?咱們再來看下Redux的工做流程。架構
首先,咱們看下幾個核心概念:app
而後咱們過下整個工做流程:
到這兒爲止,一次用戶交互流程結束。能夠看到,在整個流程中數據都是單向流動的,這種方式保證了流程的清晰。
前端複雜性的根本緣由是大量無規律的交互和異步操做。
變化和異步操做的相同做用都是改變了當前View的狀態,可是它們的無規律性致使了前端的複雜,並且隨着代碼量愈來愈大,咱們要維護的狀態也愈來愈多。
咱們很容易就對這些狀態什麼時候發生、爲何發生以及怎麼發生的失去控制。那麼怎樣才能讓這些狀態變化能被咱們預先掌握,能夠複製追蹤呢?
這就是Redux設計的動機所在。
Redux試圖讓每一個State變化都是可預測的,將應用中全部的動做與狀態都統一管理,讓一切有據可循。
若是咱們的頁面比較複雜,又沒有用任何數據層框架的話,就是圖片上這個樣子:交互上存在父子、子父、兄弟組件間通訊,數據也存在跨層、反向的數據流。
這樣的話,咱們維護起來就會特別困難,那麼咱們理想的應用狀態是什麼樣呢?
架構層面上講,咱們但願UI跟數據和邏輯分離,UI只負責渲染,業務和邏輯交由其它部分處理,從數據流向方面來講, 單向數據流確保了整個流程清晰。
咱們以前的操做能夠複製、追蹤出來,這也是Redux的主要設計思想。
綜上,Redux能夠作到:
Redux做者在Redux.js官方文檔Motivation一章的最後一段明確提到:
Following in the steps of Flux, CQRS, and Event Sourcing , Redux attempts to make state mutations predictable by imposing certain restrictions on how and when updates can happen.
咱們就先了解下Flux、CQRS、ES(Event Sourcing 事件溯源)這幾個概念。
舉個例子:咱們日常記帳有兩種方式,直接記錄每次帳單的結果或者記錄每次的收入/支出,那麼咱們本身計算的話也能夠獲得結果,ES就是後者。
與傳統增刪改查關係式存儲的區別:
優勢:
缺點:
顧名思義,「命令與查詢職責分離」-->」讀寫分離」。
總體的思想是把Query操做和Command操做分紅兩塊獨立的庫來維護,當事件庫有更新時,再來同步讀取數據庫。
看下Query端,只是對數據庫的簡單讀操做。而後Command端,是對事件進行簡單的存儲,同時通知Query端進行數據更新,這個地方就用到了ES。
優勢:
缺點:
Flux是一種架構思想,下面過程當中,數據老是「單向流動」,任何相鄰的部分都不會發生數據的「雙向流動」,這保證了流程的清晰。Flux的最大特色,就是數據的「單向流動」。
介紹完以上以後,咱們來總體作一下對比。
相同:當數據在write side發生更改時,一個更新事件會被推送到read side,經過綁定事件的回調,read side得知數據已更新,能夠選擇是否從新讀取數據。
差別:在CQRS中,write side和read side分屬於兩個不一樣的領域模式,各自的邏輯封裝和隔離在各自的Model中,而在Flux裏,業務邏輯都統一封裝在Store中。
Redux是Flux思想的一種實現,同時又在其基礎上作了改進。Redux仍是秉承了Flux單向數據流、Store是惟一的數據源的思想。
最大的區別:
Flux中容許有多個Store,可是Redux中只容許有一個,相較於Flux,一個Store更加清晰,容易管理。Flux裏面會有多個Store存儲應用數據,並在Store裏面執行更新邏輯,當Store變化的時候再通知controller-view更新本身的數據;Redux將各個Store整合成一個完整的Store,而且能夠根據這個Store推導出應用完整的State。
同時Redux中更新的邏輯也不在Store中執行而是放在Reducer中。單一Store帶來的好處是,全部數據結果集中化,操做時的便利,只要把它傳給最外層組件,那麼內層組件就不須要維持State,所有經父級由props往下傳便可。子組件變得異常簡單。
Redux去除了這個Dispatcher,使用Store的Store.dispatch()方法來把action傳給Store,因爲全部的action處理都會通過這個Store.dispatch()方法,Redux聰明地利用這一點,實現了與Koa、RubyRack相似的Middleware機制。Middleware可讓你在dispatch action後,到達Store前這一段攔截並插入代碼,能夠任意操做action和Store。很容易實現靈活的日誌打印、錯誤收集、API請求、路由等操做。
除了以上,Redux相對Flux而言還有如下特性和優勢:
目前,美團外賣後端管理平臺的上單各個模塊已經逐步替換爲React+Redux開發模式,流程的清晰爲錯誤追溯和代碼維護提供了便利,現實工做中也大大提升了人效。
查看源碼的話先從GitHub把這個地址上拷下來,切換到src目錄,
看下總體結構:
其中utils下面的Warning.js主要負責控制檯錯誤日誌的輸出,咱們直接忽略index.js是入口文件,createStore.js是主流程文件,其他4個文件都是輔助性的API。
咱們先結合下流程分析下對應的源碼。
首先,咱們從Redux中引入createStore方法,而後調用createStore方法,並將Reducer做爲參數傳入,用來生成Store。爲了接收到對應的State更新,咱們先執行Store的subscribe方法,將render做爲監聽函數傳入。而後咱們就能夠dispatchaction了,對應更新view的State。
那麼咱們按照順序看下對應的源碼:
入口文件,上面一堆檢測代碼忽略,看紅框標出部分,它的主要做用至關於提供了一些方法,這些方法也是Redux支持的全部方法。
而後咱們看下主流程文件:createStore.js。
createStore主要用於Store的生成,咱們先整理看下createStore具體作了哪些事兒。
首先,一大堆類型判斷先忽略,能夠看到聲明瞭一系列函數,而後執行了dispatch方法,最後暴露了dispatch、subscribe……幾個方法。這裏dispatch了一個init Action是爲了生成初始的State樹。
咱們先挑兩個簡單的函數看下,getState和replaceReducer,其中getState只是返回了當前的狀態。replaceReducer是替換了當前的Reducer並從新初始化了State樹。這兩個方法比較簡單,下面咱們在看下其它方法。
訂閱函數的主要做用是註冊監聽事件,而後返回取消訂閱的函數,它把全部的訂閱函數統一放一個數組裏,只維護這個數組。
爲了實現實時性,因此這裏用了兩個數組來分別處理dispatch事件和接收subscribe事件。
store.subscribe()方法總結:
再來看下store.dispatch()-->分發action,修改State的惟一方式。
store.dispatch()方法總結:
到這兒的話,主流程咱們就講完了,下面咱們講下幾個輔助的源碼文件。
bindActionCreators把action creators轉成擁有同名keys的對象,使用dispatch把每一個action creator包裝起來,這樣能夠直接調用它們。
實際狀況用到的並很少,唯一的應用場景是當你須要把action creator往下傳到一個組件上,卻不想讓這個組件覺察到Redux的存在,並且不但願把Redux Store或dispatch傳給它。
這個方法的主要功能是用來合併Reducer,由於當咱們應用比較大的時候Reducer按照模塊拆分看上去會比較清晰,可是傳入Store的Reducer必須是一個函數,因此用這個方法來做合併。代碼不復雜,就不細講了。它的用法和最後的效果能夠看下上面左側圖。
compose這個方法,主要用來組合傳入的一系列函數,在中間件時會用到。能夠看到,執行的最終結果是把各個函數串聯起來。
中間件是Redux源碼中比較繞的一部分,咱們結合用法重點看下。
首先看下用法:
const store = createStore(reducer,applyMiddleware(…middlewares)) or const store = createStore(reducer,{},applyMiddleware(…middlewares))
能夠看到,是將中間件做爲createStore的第二個或者第三個參數傳入,而後咱們看下傳入以後實際發生了什麼。
從代碼的最後一行能夠看到,最後的執行代碼至關於applyMiddleware(…middlewares)(createStore)(reducer,preloadedState)而後咱們去applyMiddleware裏看它的執行過程。
能夠看到執行方法有三層,那麼對應咱們源碼看的話最終會執行最後一層。最後一層的執行結果是返回了一個正常的Store和一個被變動過的dispatch方法,實現了對Store的加強。
這裏假設咱們傳入的數組chain是[f,g,h],那麼咱們的dispatch至關於把原有dispatch方法進行f,g,h層層過濾,變成了新的dispatch。
由此的話咱們能夠推出中間件的寫法:由於中間件是要多個首尾相連的,須要一層層的「加工」,因此要有個next方法來獨立一層確保串聯執行,另外dispatch加強後也是個dispatch方法,也要接收action參數,因此最後一層確定是action。
再者,中間件內部須要用到Store的方法,因此Store咱們放到頂層,最後的結果就是:
看下一個比較經常使用的中間件redux-thunk源碼,關鍵代碼只有不到10行。
做用的話能夠看到,這裏有個判斷:若是當前action是個函數的話,return一個action執行,參數有dispatch和getState,不然返回給下箇中間件。
這種寫法就拓展了中間件的用法,讓action能夠支持函數傳遞。
咱們來總結下這裏面的幾個疑點。
由於中間件是要多個首尾相連的,對next進行一層層的「加工」,因此next必須獨立一層。那麼Store和action呢?Store的話,咱們要在中間件頂層放上Store,由於咱們要用Store的dispatch和getState兩個方法。action的話,是由於咱們封裝了這麼多層,其實就是爲了做出更高級的dispatch方法,是dispatch,就得接受action這個參數。
咱們用applyMiddleware是爲了改造dispatch的,因此applyMiddleware執行完後,dispatch是變化了的,而middlewareAPI是applyMiddleware執行中分發到各個middleware,因此必須用匿名函數包裹dispatch,這樣只要dispatch更新了,middlewareAPI中的dispatch應用也會發生變化。
由於咱們的dispatch是用匿名函數包裹,因此在中間件裏執行dispatch跟其它地方沒有任何差異,而執行next至關於調用下箇中間件。
到這兒爲止,源碼部分就介紹完了,下面總結下開發中的最佳實踐。
官網中對最佳實踐總結的很到位,咱們重點總結下如下幾個:
瑩瑩,美團外賣前端研發工程師,2016年加入美團外賣,負責外賣商家管理平臺以及銷售人員App蜜蜂的整個上單流程開發。
最後,附上一條硬廣,美團外賣長期誠聘高級前端工程師/前端技術專家,歡迎發送簡歷至:tianhuan02#meituan.com。