引言:爲何數據流管理如此重要?react的核心思想就是:UI=render(data),data就是咱們說的數據流,render是react提供的純函數,因此用戶界面的展現徹底取決於數據層。這篇文章但願能用最淺顯易懂的話,將react中的數據流管理,從自身到藉助第三方庫,將這些概念理清楚。我會列舉幾個當下最熱的庫,包括它們的思想以及優缺點,適用於哪些業務場景。這篇文章不是教程,不會講如何去使用它們,更不會一言不合就搬源碼,正如文章標題所說,只是淺談,但願讀者在讀完之後就算原先沒有使用過這些庫,也能大體有個思路,知道該如何選擇性地深刻學習。前端
在本文正式開始以前,我先試圖講清楚兩個概念,狀態和數據:react
咱們都知道,react是利用可複用的組件來構建界面的,組件本質上是一個有限狀態機,它可以記住當前所處的狀態,而且可以根據不一樣的狀態變化作出不一樣的操做。在react中,把這種狀態定義爲state,用來描述該組件對應的當前交互界面,表示當前界面展現的一種情況,react正是經過管理狀態來實現對組件的管理,當state發生變動時,react會自動去執行相應的操做:繪製界面。web
因此狀態是針對react component這種有限狀態機纔出現的名詞,而數據就普遍了,它不光是指server層返回給前端的數據,react中的狀態也是一種數據,當咱們改變數據的同時,就要經過改變狀態去引起界面的變動;咱們真正要關心的是數據層的管理,咱們今天所討論的數據流管理方案,特別是後面介紹的幾種第三方庫,不光是配合react,也能夠配合其餘的View框架(Vue、Angular等等),就比如開頭提到的那個公式,引伸一下:UI = X(data),但今天主要是圍繞react來說的,所以咱們在說react的狀態管理其實和數據流管理是同樣的,包括咱們會藉助第三方庫來幫助react管理狀態,但願不要有小夥伴太糾結於此。typescript
一,react自身的數據流管理方案express
咱們先來回顧一下,react自身是如何管理數據流的(也能夠理解爲如何管理應用狀態):編程
react是自上而下的單向組件數據流,容器組件&展現組件(也叫傻瓜組件&聰明組件)是最經常使用的react組件設計方案,容器組件負責處理複雜的業務邏輯以及數據,展現組件負責處理UI層,一般咱們會將展現組件抽出來進行復用或者組件庫的封裝,容器組件自身經過state來管理狀態,setState更新狀態,從而更新UI,經過props將自身的state傳遞給展現組件實現通訊。redux
這是當業務需求不復雜,頁面較簡單時咱們經常使用的數據流處理方式,僅用react自身提供的props和state來管理足矣,可是若是稍微增長一點複雜度呢,好比當咱們項目中遇到這些問題:設計模式
1,如何實現跨組件通訊、狀態同步以及狀態共享?api
react V16.3之前,經過狀態提高至最近的共同父組件來實現。(雖然有官方提供的context API,可是舊版本存在一個問題:看似跨組件,實則仍是逐級傳遞,若是中間組件使用了ShouldComponentUpdate檢測到當前state和props沒有變化,return false,那麼context就會沒法透傳,所以context沒有被官方推薦使用)。app
react V16.3版本之後,新版本context解決了以前的問題,能夠輕鬆實現,但依然存在一個問題,context也是將底部子組件的狀態控制交給到了頂級組件,可是頂級組件狀態更新的時候必定會觸發全部子組件的re-render,那麼也會帶來損耗。(雖然咱們能夠經過一些手段來減小重繪,好比在中間組件的SCU裏進行一些判斷,可是當項目較大時,咱們須要花太多的精力去作這件事)
2,如何避免組件臃腫?
當某個組件的業務邏輯很是複雜時,咱們會發現代碼越寫越多,由於咱們只能在組件內部去控制數據流,沒辦法抽離,Model和View都放在了View層,整個組件顯得臃腫不堪,業務邏輯通通堆在一塊,難以維護。
3,如何讓狀態變得可預知,甚至可回溯?
當數據流混亂時,咱們一個執行動做可能會觸發一系列的setState,咱們如何可以讓整個數據流變得可「監控」,甚至能夠更細緻地去控制每一步數據或狀態的變動?
4,如何處理異步數據流?
react自身並未提供多種處理異步數據流管理的方案,僅用一個setState已經很難知足一些複雜的異步流場景;
如何改進?
這個時候,咱們可能須要一個真正的數據流管理工具來幫助react了,咱們但願它是真正脫離react組件的概念的,從UI層徹底抽離出來,只負責管理數據,讓react只專一於View層的繪製,那這也是爲何咱們須要使用那些第三方數據流管理工具的緣由,接下來咱們就來了解一些當前社區比較熱門的數據流管理工具。
二,redux
我直接跳過了flux來講redux,主要是由於redux是由flux演變而來,能夠說是flux的升級增強版,flux具有的優點redux也作到了。
redux提供了哪些?
1,store:提供了一個全局的store變量,用來存儲咱們但願從組件內部抽離出去的那些公用的狀態;
2,action:提供了一個普通對象,用來記錄咱們每一次的狀態變動,可日誌打印與調試回溯,而且這是惟一的途徑;
3,reducer:提供了一個純函數,用來計算狀態的變動;
爲何須要redux?
不少人在用了一段時間的redux以後,最大的感想就是,redux要寫大量的模板代碼,很麻煩,還不如只用react來管理。特別是在react的新context推出之後,許多人更是直接棄用了redux,甚至以爲redux已死。若是說舊版的context的弊端,咱們經過redux配合react-redux來實現跨組件的狀態通訊同步等問題,那確實新版本的context能夠替換掉這個功能點,但若是你的項目中僅僅是用redux作這些,那思考一下,你是否真的須要redux?也許從一開始你就不須要它。(雖然新版的context功能強大,可是依然是經過一個新的容器組件來替咱們管理狀態,那麼經過組件管理狀態的問題依舊會存在,Consumer是和Provider一一對應的,在項目複雜度較高時,可能會出現多個Provider,更多個Consumer,甚至會一個Consumer須要對應多個Provider的傳值等一系列複雜的狀況,因此咱們依然要謹慎使用)
redux的核心競爭力
1,狀態持久化:global store能夠保證組件就算銷燬了也依然保留以前狀態;
2,狀態可回溯:每一個action都會被序列化,Reducer不會修改原有狀態,老是返回新狀態,方便作狀態回溯;
3,Functional Programming:使用純函數,輸出徹底依賴輸入,沒有任何反作用;
4,中間件:針對異步數據流,提供了類express中間件的模式,社區也出現了一大批優秀的第三方插件,可以更精細地控制數據的流動,對複雜的業務場景起到了緩衝地做用;
與其說是redux來幫助react管理狀態,不如說是將react的部分狀態移交至redux那裏,redux徹頭徹尾的純函數理念就代表了它不會參與任何狀態變化,徹底是由react本身來完成,只不過redux會提供一套工具,react照着說明書來操做罷了,因此這注定了想要接受redux,就必須按照它的規矩來作,除非你不肯意接受這種FP的模式。這種模式有利有弊,有利就是在一個大型的多人團隊中,這種開發模式反而容易造成一種規約,讓整個狀態流程變得清晰,弊端就是對於小規模團隊,尤爲是着急發佈上線的,這種繁重的代碼模板無疑是一種負擔。
redux的缺點:
1,繁重的代碼模板:修改一個state可能要動四五個文件,可謂牽一髮而動全身;
2,store裏狀態殘留:多組件共用store裏某個狀態時要注意初始化清空問題;
3,無腦的發佈訂閱:每次dispatch一個action都會遍歷全部的reducer,從新計算connect,這無疑是一種損耗;
4,交互頻繁時會有卡頓:若是store較大時,且頻繁地修改store,會明顯看到頁面卡頓;
5,不支持typescript;
關於如何優化,網上有不少優秀的案例,redux官方也提供了不少方法,這裏再也不贅述。redux將來不會有太大的變化,那些弊端仍是會繼續保留,可是這依然不會妨礙忠愛它的用戶去使用它。
若是說redux那種強硬的函數式編程模式讓不少人難以接受,那麼當他們開始mobx的使用的時候,無疑是一種解脫。
三,mobx
最開始接觸mobx也是由於redux做者Dan Abramov的那句:Unhappy with redux?try mobx,我相信不少人也是由於這句話而開始瞭解學習並使用它的。
下面列舉一些mobx的優點(和redux進行一個對比)
1,redux不容許直接修改state,而mobx可隨意修改;
2,redux修改狀態必須走一套指定的流程較麻煩,而mobx能夠在任何地方直接修改(非嚴格模式下);
3,redux模板代碼文件多,而mobx很是簡潔,就一個文件;
4,redux只有一個store,state or store難以取捨,而mobx多store,你能夠把全部的state都放入store中,徹底交給mobx來管理,減小顧慮;
5,redux須要對監聽的組件作SCU優化,減小重複render;而mobx都是Smart Component,不用咱們手動作SCU;
mobx的設計思想:
說了這麼多,若是你是第一次瞭解mobx,是否是聽着就感受很爽!沒錯,這就是mobx的魅力,那它是如何實現這些功能的呢?這裏以mobx 5版本爲例,實際上它是利用了ES6的proxy來追蹤屬性(舊版本是用Object.defineProperty來實現的)經過隱式訂閱,自動追蹤被監聽的對象變化,而後觸發組件的UI更新;若是說redux是把要作的事情都交給了用戶,來保證本身的純淨,那麼mobx就是把最簡易的操做給了用戶,其它的交給mobx內部去實現,用戶沒必要關心這個過程,Model和View徹底分離,咱們徹底能夠將業務邏輯寫在action裏,用戶只須要操做Observable data就好了,Observer view會自動作出響應,這就是mobx主打的響應式設計,可是編程風格依然是傳統的面向對象的OO範式。(熟悉Vue的朋友必定對這種響應式設計不陌生,Vue就是利用了數據劫持來實現雙向綁定,其實React + Mobx就是一個複雜點的Vue,Vue 3版本一個重大改變也是將代理交給了proxy)
剛剛mobx的優點說得比較多了,這邊再總結一下:
1,代碼量少;
2,基於數據劫持來實現精準定位(真正意義上的局部更新);
3,多store抽離業務邏輯(Model View分離);
4,響應式性能良好(頻繁的交互依然能夠勝任);
5,徹底能夠替代react自身的狀態管理;
6,支持typescript;
可是mobx真的這麼完美嗎,固然也有缺陷:
1,沒有狀態回溯能力:mobx是直接修改對象引用,因此很難去作狀態回溯;(這點redux的優點就瞬間體現出來了)
2,沒有中間件:和redux同樣,mobx也沒有很好地辦法處理異步數據流,沒辦法更精細地去控制數據流動;(redux雖然本身不作,可是它提供了applyMiddleware!)
3,store太多:隨着store數的增多,維護成本也會增長,並且多store之間的數據共享以及相互引用也會容易出錯
4,反作用:mobx直接修改數據,和函數式編程模式強調的純函數相反,這也致使了數據的不少未知性
其實如今主流的數據流管理分爲兩大派,一類是以redux爲首的函數式庫,還有一類是以mobx爲首的響應式庫,其實經過剛剛的介紹,咱們會發現,redux和mobx有一個共同的短板,那就是在處理異步數據流的時候,沒有一個很好的解決方案,至少僅僅依靠自身比較吃力,那麼接下來給你們介紹一個處理異步數據流的高手:rxjs。
四,rxjs
我相信不少人據說過rxjs學習曲線異常陡峭,是的,除了眼花繚亂的各種操做符(目前rxjs V6版本有120+個),關鍵是它要求咱們在處理事務的時候要貫徹「一切皆爲流」的理念,更是讓初學者難以理解。這一小節並不能讓讀者達到可以上手使用的程度,正如文章開頭所說,但願讀者(新手)能對rxjs有一個新的認知,知道它是作什麼的,它是如何實現的,它有哪些優點,咱們如何選擇它,若是感興趣還須要私下花大量時間去學習掌握各類操做符,但我也會嘗試儘量地相對於前兩個說得更細緻一些。
在開始介紹rxjs以前,咱們先來簡單地聊聊什麼是響應式編程?我以一個很簡單的小例子來看:a + b = c;若是站在傳統的命令式編程的角度來看這段公式:c的值徹底依賴於a和b,這時候咱們去改變a的值,那咱們就須要再去手動計算a + b的值,a、b和c是相互依賴的;那麼若是站在響應式編程的角度來看,這個公式又會變成這樣:
c := a + b,a和b徹底不關心c的值,c也徹底不關心等式那邊是a或者b,或者還有什麼d,e,f。。。等式右邊改變值了,左邊會自動更改數值,這就是響應式編程的思惟方式。咱們再來看前端的框架歷史,傳統命令式編程的表明:jQuery,在過去咱們是如何繪製一個頁面的?咱們會用jQuery提供的一套API,而後手動操做Dom來進行繪製,很精準,可是很累,由於徹底靠手動操做,且改動時性能損耗較大,開發者的注意力徹底在「如何去繪製」上面了;那咱們再來看響應式編程的react,它是如何來實現的?開發者根本不用關心界面如何繪製,只要告訴react咱們但願頁面長什麼樣子,就能夠了,剩下的交給react,react就會自動幫咱們繪製界面,還記得開頭時的那個核心思想嗎:UI = render(data),咱們只要操做data就能夠了,頁面UI會自動做出響應,並且咱們一切的操做都是基於內存之中,不會有較大的性能損耗,這就是react響應式編程的精髓,也是爲什麼它叫做react。
回到咱們的rxjs上,rxjs是如何作到響應式的呢?多虧了它兩種強大的設計模式:觀察者模式和迭代器模式;簡單地介紹一下:
1,觀察者模式:
在觀察者模式中,有兩個重要的角色:Observable和Observer,熟悉mobx的同窗對這個必定不陌生(因此我建議想要學習rxjs的同窗,若是對mobx不熟悉,能夠先學習一下mobx,而後再學習rxjs,這樣會更容易理解一些),就是可觀察對象和觀察者,可觀察對象(Observable)也就是事件發佈者,負責產生事件,而觀察者(Observer)也就是事件響應者,負責對發佈的事件做出響應,可是如何鏈接一個發佈者和響應者呢?經過訂閱的形式,也就是subscribe方法(這也相似於redux的store.subscribe),而在訂閱以前,他們二者是毫無關聯的,不管Observable發出多少事件,Observer也不會作出任何響應,一樣,當這種訂閱關係中斷時也不會;
2,迭代器模式:
在這裏要先引出一個新的概念:拉取(pull)和推送(push),rxjs官方這兩種協議有更詳細的解釋,我這裏就直接引用一下:
拉取和推送實際上對於觀察者來講就是一個主動與被動的區別,是主動去獲取仍是被動地接收。在rxjs中,做爲事件響應者(消費者)的Observer對象也有一個next屬性(回調函數),用來接收從發佈者那裏「推」過來的數據。(站在開發者的角度,咱們必定是但願消息是被動地接收,由於咱們倡導的就是經過操做data數據層,讓View層進行一個響應,那麼這裏data數據層必定是事件發佈者,而View層就是事件響應者,每當data數據層發生變化時,都會主動推送一個值給View層,這才符合真正意義上的響應式編程,而rxjs作到了!)
如何配合react?
若是說redux和mobx的出現或多或少是由於react的存在,那麼不一樣的是rxjs和react並無什麼關聯,關於rxjs的歷史這裏很少說,感興趣的能夠了解一下Reactive Extension,rxjs只是響應式編程在JavaScript中的應用。那麼如何幫助react實現狀態管理呢,咱們只須要將組件做爲事件響應者,而後在next回調裏定義好更新組件狀態的動做setState,當接收到數據推送時,就會自動觸發setState,完成界面更新,這其實有點相似於mobx作的事情。(不少人在react項目中並無徹底只使用rxjs,而是用了這個redux-observable中間件,利用rxjs的操做符來處理異步action)
除了響應式編程的魅力,rxjs還有什麼優點呢?
1,純函數:rxjs中數據流動的過程當中,不會改變已經存在的Observable實例,會返回一個新的Observable,沒有任何反作用;
2,強大的操做符:rxjs又被稱爲lodash for async,和lodash同樣,擁有衆多強大的操做符來操做數據流,不光是同步數據,特別是針對各類複雜的異步數據流,甚至能夠多種事件流組合搭配,彙總到一塊兒處理;
3,更獨立:rxjs並不依賴於任何一個框架,它能夠任意搭配,由於它的關注點徹底就是在於數據流的處理上,並且它更偏底層一些
那rxjs有哪些缺點呢?
1,學習曲線陡峭:光是這一點就已經讓大多數人止步於此;
2,事件流高度抽象:用rxjs的用戶反饋通常都是兩種極端狀況,用得好的都以爲這個太厲害了,用得很差的都以爲感受有點麻煩,增長了項目複雜度。
最後,總結一下各種的適用場景:
1,當咱們項目中複雜程度較低時,建議只用react就能夠了;
2,當咱們項目中跨組件通訊、數據流同步等狀況較多時,建議搭配react的新context api;
3,當項目複雜度通常時,小規模團隊或開發週期較短時,建議使用mobx;
4,當項目複雜度較高時,團隊規模較大或要求對事件分發處理可監控可回溯時,建議使用redux;
5,當項目複雜度較高,且數據流混雜時,建議使用rxjs;
結語:其實回顧全篇,我沒有提到一個關鍵點是,各個庫的性能對好比何。其實它們之間必定是有差別的,可是這點性能差別,相對於react自身組件設計不當而致使的性能損耗來講,是能夠忽略的,若是你如今的項目以爲性能較差或者頁面卡頓,建議先從react層面去考慮如何進行優化,而後再去考慮如何優化數據管理層。關於上面提到的三個數據流管理工具,有利有弊,針對弊端,網上也有一大批優秀的解決方案和改進,感興趣的讀者可自行查閱。
謝謝!
=============
ps:這篇文章會發布在攜程公衆號上,不是抄襲,我就是做者,先更新到博客園上而已。