前言
在React中,數(shù)據(jù)流是單向的,并且是不可逆的,這其實,也很好理解,之所以這么設(shè)計,是因為組件復(fù)用的特點
父(外部)組件向子(內(nèi)部)組件傳遞數(shù)據(jù)是通過自定義屬性props值的方式進行實現(xiàn)的,并且在子組件內(nèi)部通過this.props進行獲取,它并不能直接被修改,如果想要修改,那么得通過React內(nèi)置的一個setState的方法進行觸發(fā)
而子組件想要傳遞數(shù)據(jù)給父組件,是通過調(diào)用父組件的方法進行通信
一個組件可能存在著很多狀態(tài),組件之間有時需要進行通信,對于多個組件狀態(tài)維護,如果依舊用原來的方式,那么就比較復(fù)雜了的
那么Redux正好解決了這一問題.個人覺得,Redux學(xué)起來很抽象,的確是塊硬骨頭,但是高山始終是要越過的
下面就一起來學(xué)習(xí)下Redux的
您將在本文中學(xué)習(xí)到
Redux是什么
Redux的使用場景以及與不使用Redux的靈魂對比
Redux的工作流程
Redux的設(shè)計基本原則
本篇雖不涉及代碼層面上的,但是對后續(xù)編碼Redux非常重要,磨刀不誤砍柴工
如果想閱讀體驗更好,可戳鏈接,React進階(1)-理解Redux
Redux是什么?
官方解釋:JavaScript應(yīng)用程序的可預(yù)測的狀態(tài)容器(一個管理應(yīng)用程序狀態(tài)的框架)
通俗一點:管理組件公共數(shù)據(jù)狀態(tài)的容器(倉庫/區(qū)域)
解決的問題: 當(dāng)應(yīng)用組件擁有多個狀態(tài),并且組件之間需要共享數(shù)據(jù)狀態(tài)時,從原始的組件傳遞數(shù)據(jù)的方式中解脫出來,集中管理組件的狀態(tài)
你可以把Redux理解為一個倉庫,房產(chǎn)中介.擁有很多共享的房源的一個管理者,后面會有具體的例子
Redux的使用場景
從上面提到的Redux解決問題可以看出,Redux只是用來管理和維護組件的狀態(tài)的
React開發(fā)的模式就是組件化開發(fā),將一個大的應(yīng)用拆分成若干個小的應(yīng)用,然后拼接成一個大的應(yīng)用,而編寫一個大小應(yīng)用就是在編寫各個大小組件
而組件的顯示形態(tài)又取決于它的狀態(tài),這不區(qū)分于無論是外部的props還是內(nèi)部的state,而組件之間有時需要共享傳遞數(shù)據(jù),Redux僅僅就是用來管理這些組件的狀態(tài)的
在一些開發(fā)者眼里,項目里要是沒有用到Redux,就覺得很low,要么把Redux捧得高高在上,要么說都已經(jīng)快0202年了,都用React hook了,鄙視得不行,個人覺得完全沒有必要.
React與Redux本身就是解決兩個不同方向的問題,某種程度上講,React可以視為MVC架構(gòu)中的視圖層V,而Redux則是model數(shù)據(jù)層M,而C層往往是連接視圖層和model的連接器,往往處理前端數(shù)據(jù)請求,路由跳轉(zhuǎn)等業(yè)務(wù)邏輯
即使不用Redux,照樣能做小應(yīng)用,只是略復(fù)雜繁瑣一些而已,下面會介紹他們之間的對比
那么對于技術(shù)選型,什么時候用Redux什么時候不用?
以下是選用Redux的場景:
項目非常龐大,公共組件與業(yè)務(wù)組件非常多,用戶的使用方式比較復(fù)雜
不同身份的用戶角色權(quán)限管理(例如很多后臺管理系統(tǒng),普通用戶,超級管理員,VIP用戶)讀,寫權(quán)限管理等
多個用戶之間可以協(xié)作實時操作(很多那種在線敏捷協(xié)作辦公文檔工具,多個用戶可以實時編輯操作同一份文檔等的,例如石墨文檔,語雀,confluence.釘釘?shù)鹊?
需要與服務(wù)器大量的交互,或者使用了webscoket的,聊天,直播等應(yīng)用的
視圖層view需要從多個來源獲取數(shù)據(jù)
....只要你發(fā)現(xiàn)React解決不了的問題,遇到多交互,多數(shù)據(jù)源的,那么就可以考慮使用Redux的
反之,則以下則是沒有必要使用Redux
UI層非常簡單,只是用于渲染,無復(fù)雜的數(shù)據(jù)交互,依賴外部的props就可以渲染組件
用戶的使用方式比較簡單,頁面之間比較獨立,沒有互相協(xié)作
與服務(wù)器之間沒有大量交互
當(dāng)你發(fā)現(xiàn)使用React實在解決不了的問題,在各個組件之間傳遞數(shù)據(jù)非常復(fù)雜,很痛苦時,那么就可以考慮使用Redux了的,只要你hold住,沒有所謂的高大上技術(shù),只有適合自己業(yè)務(wù)的技術(shù)
盲目引入Redux只會增加項目的復(fù)雜度,引入新的技術(shù)應(yīng)該是循序漸進的
不使用Redux與使用Redux的靈魂對比
下面這張組件樹狀態(tài)圖的對比就很好的解釋了使用Redux與不使用Redux的區(qū)別
一個React應(yīng)用(例如:pc網(wǎng)站,手機app應(yīng)用,后臺管理系統(tǒng)等用React技術(shù)棧構(gòu)建的應(yīng)用)其實就是一顆由組件構(gòu)成的樹,如上圖所示,在這顆樹的根結(jié)點,最頂層的組件就是該應(yīng)用的本身,由于組件都是以樹結(jié)構(gòu)組織起來的,當(dāng)每個組件被渲染時,它都會遞歸地渲染下級組件
假設(shè)紅色圓圈代表的是一個應(yīng)用的子組件,如果想要把該紅色圓圈組件的狀態(tài)數(shù)據(jù)傳遞給父級或者非父級組件,它是通過調(diào)用父組件的方法來實現(xiàn),這樣一層一層往上傳,如果組件樹很龐大的話,那么就會變得非常繁瑣
在小型項目中,Redux并不是必需的,但是使用Redux卻是一勞永逸的,管理組件的狀態(tài)方便得多,對于大型應(yīng)用來說,單純使用原始的數(shù)據(jù)傳遞方式
那么組件之間的傳值會變得非常復(fù)雜,如果要做一個大型的應(yīng)用,那么就需要在React的基礎(chǔ)上配置一個數(shù)據(jù)層的框架進行結(jié)合的使用
如果改為右邊的Redux處理方式,將紅色圓圈組件的狀態(tài)數(shù)據(jù)放到一個Store倉庫當(dāng)中集中進行管理,哪個組件需要的話,直接派發(fā)給哪個組件就可以了的.
在Redux中,要求把組件的數(shù)據(jù)放到公共的存儲倉庫(區(qū)域)當(dāng)中,讓組件盡可能的減少狀態(tài)數(shù)據(jù)存儲,換而言之,所有組件自身內(nèi)部狀態(tài)數(shù)據(jù)都不放在state里面了,把它放到Store這樣的一個存儲倉庫當(dāng)中去
其實本質(zhì)上來說,是放到reducer里面去管理,Store從Reducer中拿到返回的數(shù)據(jù)state,最后供外部組件的取用
當(dāng)紅色圓圈組件想要改變數(shù)據(jù)傳遞給其他組件時,只需要去改變Store里面的存儲紅色圓圈組件的數(shù)據(jù)就可以了
一旦Store公共存儲的狀態(tài)數(shù)據(jù)發(fā)生改變了的,由于其他組件是公用Store的數(shù)據(jù),那么其他組件就會感知到Store的數(shù)據(jù)發(fā)生了改變,從而自身組件也會跟著改變
只要Store公共存儲區(qū)域的數(shù)據(jù)發(fā)生改變,凡是共用了Store里面的數(shù)據(jù)的組件都會重新的取數(shù)據(jù)
這樣一來,紅色圓圈組件的數(shù)據(jù)就非常容易的傳遞給其他組件了,無論是它的父級組件還是兄弟,非兄弟組件的
Redux就是把組件的數(shù)據(jù)放到一個公共的區(qū)域(倉庫)中進行存儲,當(dāng)改變Store存儲區(qū)域里面的數(shù)據(jù)時,其他組件如果用到了公共區(qū)域的數(shù)據(jù),那么就會感知到數(shù)據(jù)的變化,它會自動的更新取Store中最新的數(shù)據(jù)
這樣話,無論你的應(yīng)用組件嵌套得有多么復(fù)雜,多么深,走的流程都是一樣的,組件之間并不會干擾,低耦合的效果
當(dāng)組件一修改,把修改的數(shù)據(jù)放到Store當(dāng)中,而其他組件又從Store當(dāng)中再來取,這樣的話,組件與組件之間并不是直接進行通信的,是通過這么一個store中間角色來實現(xiàn)數(shù)據(jù)的傳遞共享的.
這樣的話,組件的數(shù)據(jù)傳遞就簡單多了的,也避免了組件與組件之間頻繁通信,容易產(chǎn)生混亂的問題
Redux其實是Flux數(shù)據(jù)框架的一個替代演進,同樣強調(diào)的是單向的數(shù)據(jù)源,保持狀態(tài)只具備讀的能力,而數(shù)據(jù)改變只能通過純函數(shù)完成基本,這和原先中React的UI=render(data)完全吻合.
React與Redux是兩個獨立的框架,前者是用于組件視圖層的渲染,而后者是管理組件的數(shù)據(jù)
Redux的工作流程
現(xiàn)在已經(jīng)知道了使用Redux與不使用Redux的區(qū)別,那么現(xiàn)在是時候來了解一下Redux的工作流程了,下面這個流程圖對于理解Redux很重要 先附上一張Redux工作流的流程圖:以后會在代碼中逐步的體現(xiàn)
上面的Redux工作流圖中,以中間為準(zhǔn):包括了Store,ReactComponents,Actions Creators,以及Reducers
其中Store代表的就是負(fù)責(zé)組件存儲所有公共狀態(tài)的數(shù)據(jù),全局只有一個Store.(這里你可以把它理解為類似生活當(dāng)中中介公司管理房源的倉庫(數(shù)據(jù)庫)的區(qū)域經(jīng)理)
實質(zhì)上:store就是把Reducer關(guān)聯(lián)到一起的一個對象,它提供dispatch(action)方法更新state,以及getState方法獲取state
React Components:指的是頁面上的任意一個組件(你可以理解為小區(qū)公寓樓里的每個房間,而你就是住在里面的租房用戶)
Actions Creators:具體要干什么事情,觸發(fā)的動作,可以看做一個交互動作,改變應(yīng)用狀態(tài)或view的更新,都需要通過觸發(fā)action來實現(xiàn),Action的執(zhí)行結(jié)果就是調(diào)用Dispatch來處理相應(yīng)的事情,實現(xiàn)頁面視圖view的更新,唯一的辦法就是調(diào)用dispatch派發(fā)action
它是一個javascript對象,是用來描述事件的行為的,對象里記錄了相關(guān)的信息,例如:todolist的添加,刪除list的這個具體操作,就是一個action
(當(dāng)你想要提出換房的時候,跟中介公司管理房源的經(jīng)理說,你要換個帶有沙發(fā),電視,配備廚房的兩室一廳的房子,因為增加人口了,現(xiàn)有的房子住不下了的,你要做的什么事情,提出的條件信息就是數(shù)據(jù)),這個動作可以理解為actions creators
在你提出換房的時候,房產(chǎn)中介公司經(jīng)理雖然手握很多房源,但是他也沒有辦法記得所有的房子相關(guān)信息,它需要去數(shù)據(jù)庫(倉庫)里去查,你常澈雎澹看到中介小哥帶你看房的時候
手上拿一個單子,Excel表格跟你介紹房源的時候,你可以把這個單子,Excel表格理解為一個實時記錄本,只要有房子出租去了,這個表格就會實時更新(新舊信息的核實對比),返回一張新的房源信息表單給房產(chǎn)中介的經(jīng)理
Reducer:可以把上面的用于實時更新記錄房源信息的記錄本稱為Reducer,它只用作于根據(jù)舊的房源與提出新的需求(動作),總是會返回一張新的記錄本給房產(chǎn)中介經(jīng)理
實質(zhì)上:Reducer是根據(jù)action發(fā)出的type(動作類型)來做什么事(返回最新的state給store等邏輯操作)
現(xiàn)在歸納一下整個流程:
我(租客/組件React Component)想要換一個xxx信息的房子(Actions creators,具體要做的什么事情),房產(chǎn)中介經(jīng)理收到了請求,他得根據(jù)你提供的一些需求信息去找相應(yīng)的房源信息
但是房源太多,需要借助一個實時的記錄本去查看符合條件的房源信息,當(dāng)查到符合條件的信息后,這個記錄本(Reducer)把最新的信息會返回給房產(chǎn)經(jīng)理(Store),最終把信息返回給用戶React Components,實現(xiàn)房子替換的更新
雖然文字啰嗦了點:但是Redux就是這么一回事,我要換大房子,房產(chǎn)中介經(jīng)理聽到后,它去記錄本里面去查,查到之后,返回到房產(chǎn)中介經(jīng)理,然后最終在返回給我,實現(xiàn)房子的替換
那么轉(zhuǎn)換為代碼理解:
頁面上的一個組件,想要獲取更新Store中的數(shù)據(jù),跟Store說,我點擊這個按鈕,要更新這個組件的數(shù)據(jù),要干什么事情,做的這個具體動作就是Actions Creators,這時會派發(fā)(dispatch) 該動作(action)給Store,Store會去Reducer里面去查一下,Reducer會返回一個新的結(jié)果給Store,Store拿到最新的數(shù)據(jù)結(jié)果后,返回給頁面上的組件,實現(xiàn)頁面組件的更新
大家可以先仔細(xì)體會上面這段文字的含義,在后續(xù)的實例代碼中,在回過頭來對比著代碼與文字進行理解的,后續(xù)還會在拿出來的
Redux的設(shè)計基本原則
在Redux中有以下幾個設(shè)計基本原則
單向數(shù)據(jù)流
唯一數(shù)據(jù)源
保持狀態(tài)只讀
數(shù)據(jù)的改變只能通過純函數(shù)reducer來完成
單向數(shù)據(jù)流: 這個其實與props不能直接被修改一樣,在父組件向子組件傳遞數(shù)據(jù)時是通過屬性的方式進行傳遞的,而子組件內(nèi)部通過this.props進行接收,但是外部傳遞過來的props屬性不能直接被修改,若想要修改,需要借助React內(nèi)置的setState方法進行觸發(fā)
唯一數(shù)據(jù)源: 它指的是組件的應(yīng)用狀態(tài)數(shù)據(jù)應(yīng)該只存在唯一的Store上,這一點是不同于Flux的,在Flux中允許有多個store捞慌。而在Redux中整個組件的應(yīng)用只保持一個Store,所有組件的數(shù)據(jù)源就是這個Store上的狀態(tài),可以將它Store理解為一個全局的變量對象
保持狀態(tài)state可讀: 不能直接的去修改狀態(tài),要修改Store的狀態(tài),必須要通過派發(fā)(dispatch)一個action對象去完成
然后組件渲染的對應(yīng)的界面要更改的話,實際更改的就是組件的狀態(tài),如果狀態(tài)都是只能讀不能修改的話,那么界面就不會更新變化了
想要更改用戶界面的渲染,就要改變組件的應(yīng)用狀態(tài),但時改變組件狀態(tài)的方法不是直接去修改狀態(tài)上的值,而是創(chuàng)建一個新的狀態(tài)對象返回給Redux,由Redux完成新的狀態(tài)的組裝
組件數(shù)據(jù)的改變只能通過純函數(shù)完成
所謂的純函數(shù),就是指Reducer,而Redux某種程度上講,它是Reducer+Flux的組合,其中這Redux的Red代表就是Reducer,而ux就是Flux,但是又不同于Flux,它更像是Flux的一個實現(xiàn),演進被啼。它是為了描述Action如何改變組件的狀態(tài)的
這也是為什么Redux這個名稱比較抽象的原因,其中Reducer類似一個數(shù)組中的迭代器函數(shù)reduce
var arr = [1,2,3,4,5,6]
var sum = arr.reduce(function reducer(prevValue, currentValue,index,array){
console.log(`上一次調(diào)用回調(diào)返回的值(或者是提供的初始值): ${prevValue},數(shù)組中當(dāng)前被處理的元素: ${currentValue}, 當(dāng)前元素在數(shù)組中的索引: ${index}, 調(diào)用的數(shù)組: ${array}`);
return prevValue+currentValue;
},0)
console.log(sum); // 21
VM1742:3 上一次調(diào)用回調(diào)返回的值(或者是提供的初始值): 0,數(shù)組中當(dāng)前被處理的元素: 1, 當(dāng)前元素在數(shù)組中的索引: 0, 調(diào)用的數(shù)組: 1,2,3,4,5,6
VM1742:3 上一次調(diào)用回調(diào)返回的值(或者是提供的初始值): 1,數(shù)組中當(dāng)前被處理的元素: 2, 當(dāng)前元素在數(shù)組中的索引: 1, 調(diào)用的數(shù)組: 1,2,3,4,5,6
VM1742:3 上一次調(diào)用回調(diào)返回的值(或者是提供的初始值): 3,數(shù)組中當(dāng)前被處理的元素: 3, 當(dāng)前元素在數(shù)組中的索引: 2, 調(diào)用的數(shù)組: 1,2,3,4,5,6
VM1742:3 上一次調(diào)用回調(diào)返回的值(或者是提供的初始值): 6,數(shù)組中當(dāng)前被處理的元素: 4, 當(dāng)前元素在數(shù)組中的索引: 3, 調(diào)用的數(shù)組: 1,2,3,4,5,6
VM1742:3 上一次調(diào)用回調(diào)返回的值(或者是提供的初始值): 10,數(shù)組中當(dāng)前被處理的元素: 5, 當(dāng)前元素在數(shù)組中的索引: 4, 調(diào)用的數(shù)組: 1,2,3,4,5,6
VM1742:3 上一次調(diào)用回調(diào)返回的值(或者是提供的初始值): 15,數(shù)組中當(dāng)前被處理的元素: 6, 當(dāng)前元素在數(shù)組中的索引: 5, 調(diào)用的數(shù)組: 1,2,3,4,5,6
VM1742:6 21
上面的代碼中是做一個簡單的累加,reducer函數(shù)接收四個參數(shù),第一個參數(shù)是上一次調(diào)用返回的結(jié)果,第二個參數(shù)是當(dāng)前被處理的元素的值,第三個是當(dāng)前元素在數(shù)組中的索引,第四個是調(diào)用的原數(shù)組
這個reduce的方法接收一個函數(shù)作為累加器,reduce 為數(shù)組中的每一個元素依次執(zhí)行回調(diào)函數(shù)
而在Redux中,每個reducer純函數(shù)如下所示
其中reducer函數(shù)的第一個參數(shù)state是指當(dāng)前的狀態(tài)值,而第二個參數(shù)action是接收到的action對象
而reducer函數(shù)要做的事情就是根據(jù)state和action的值產(chǎn)生一個新的對象返回給Store,它是定義整個組件應(yīng)用狀態(tài)如何更改,根據(jù)Action動作行為去更新Store中的狀態(tài)
注意的是reducer必須是純函數(shù),換句話說,reducer函數(shù)的返回結(jié)果必須完全由參數(shù)state和action決定,而且不產(chǎn)生任何的副作用,也不能修改參數(shù)state和action對象
如下一個典型的reducer示例,reducer只是一個函數(shù)名稱,你是可以任意取的,如下一個計數(shù)的counter純函數(shù)
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
從上面的例子看得出,reducer函數(shù)不光接受action為參數(shù),還接受state參數(shù),也就是說,Redux中的reduce函數(shù)只負(fù)責(zé)計算組件的狀態(tài),卻不負(fù)責(zé)存儲組件的狀態(tài)
在Reducer函數(shù)中往往包含action.type為判斷條件的if-else或者switch語句,根據(jù)action,總是返回一個新的狀態(tài),這個新的狀態(tài)的結(jié)果返回給store,store就會將原來上一次的state進行替換更新,最終達到改變state這么一個過程
結(jié)語
本節(jié)主要介紹了Redux,它與React是兩個獨立的產(chǎn)品,兩個框架做的事情的方向不一樣,React是用作于視圖層的渲染,也相當(dāng)于MVC中的V層,而Redux它是用于管理組件公共數(shù)據(jù)的Model層,更近一步講,它是Reducer與Flux的一種結(jié)合,改進.
對比了使用Redux與不使用Redux的區(qū)別,以及Redux的工作流,最后Redux的設(shè)計基本原則,其中前兩個,個人覺得對于理解Redux是非常重要的
當(dāng)然現(xiàn)在也可以使用高階組件,React hooks的寫法,可以不用Redux了的,也有類似于dva這樣的框架,基于Redux以及中間件(Redux-saga)的數(shù)據(jù)流方案
但是Redux依然是主流,只要你能夠應(yīng)付項目中開發(fā)需求,哪個用得爽就用哪個的,Redux雖然確實是繞了一些,有時候在各個文件之間進行來回切換,對于模塊化的拆分,如果不是很清楚Redux的使用流程,無論是后續(xù)維護還是迭代升級,都挺痛苦的
本篇并不是什么高大上的內(nèi)容,比較淺顯,概念性的文字比較多,后續(xù)會結(jié)合具體的代碼進一步理解Redux