做React需要會(huì)什么桃熄?
react的功能其實(shí)很單一先口,主要負(fù)責(zé)渲染的功能,現(xiàn)有的框架瞳收,比如angular是一個(gè)大而全的框架碉京,用了angular幾乎就不需要用其他工具輔助配合,但是react不一樣螟深,他只負(fù)責(zé)ui渲染谐宙,想要做好一個(gè)項(xiàng)目,往往需要其他庫和工具的配合界弧,比如用redux來管理數(shù)據(jù)凡蜻,react-router管理路由,react已經(jīng)全面擁抱es6垢箕,所以es6也得掌握划栓,webpack就算是不會(huì)配置也要會(huì)用,要想提高性能条获,需要按需加載忠荞,immutable.js也得用上,還有單元測(cè)試帅掘。委煤。。修档。
React 是什么
用腳本進(jìn)行DOM操作的代價(jià)很昂貴碧绞。有個(gè)貼切的比喻,把DOM和JavaScript各自想象為一個(gè)島嶼吱窝,它們之間用收費(fèi)橋梁連接讥邻,js每次訪問DOM,都要途徑這座橋院峡,并交納“過橋費(fèi)”,訪問DOM的次數(shù)越多兴使,費(fèi)用也就越高。 因此撕予,推薦的做法是盡量減少過橋的次數(shù)鲫惶,努力待在ECMAScript島上蜈首。因?yàn)檫@個(gè)原因react的虛擬dom就顯得難能可貴了实抡,它創(chuàng)造了虛擬dom并且將它們儲(chǔ)存起來欠母,每當(dāng)狀態(tài)發(fā)生變化的時(shí)候就會(huì)創(chuàng)造新的虛擬節(jié)點(diǎn)和以前的進(jìn)行對(duì)比,讓變化的部分進(jìn)行渲染吆寨。整個(gè)過程沒有對(duì)dom進(jìn)行獲取和操作赏淌,只有一個(gè)渲染的過程,所以react說是一個(gè)ui框架啄清。
React的組件化
react的一個(gè)組件很明顯的由dom視圖和state數(shù)據(jù)組成六水,兩個(gè)部分涇渭分明。state是數(shù)據(jù)中心辣卒,它的狀態(tài)決定著視圖的狀態(tài)掷贾。這時(shí)候發(fā)現(xiàn)似乎和我們一直推崇的MVC開發(fā)模式有點(diǎn)區(qū)別,沒了Controller控制器荣茫,那用戶交互怎么處理想帅,數(shù)據(jù)變化誰來管理?然而這并不是react所要關(guān)心的事情啡莉,它只負(fù)責(zé)ui的渲染港准。與其他框架監(jiān)聽數(shù)據(jù)動(dòng)態(tài)改變dom不同,react采用setState來控制視圖的更新咧欣。setState會(huì)自動(dòng)調(diào)用render函數(shù)浅缸,觸發(fā)視圖的重新渲染,如果僅僅只是state數(shù)據(jù)的變化而沒有調(diào)用setState魄咕,并不會(huì)觸發(fā)更新衩椒。 組件就是擁有獨(dú)立功能的視圖模塊,許多小的組件組成一個(gè)大的組件蚕礼,整個(gè)頁面就是由一個(gè)個(gè)組件組合而成烟具。它的好處是利于重復(fù)利用和維護(hù)。
React的 Diff算法
react的diff算法用在什么地方呢奠蹬?當(dāng)組件更新的時(shí)候朝聋,react會(huì)創(chuàng)建一個(gè)新的虛擬dom樹并且會(huì)和之前儲(chǔ)存的dom樹進(jìn)行比較,這個(gè)比較多過程就用到了diff算法囤躁,所以組件初始化的時(shí)候是用不到的冀痕。react提出了一種假設(shè),相同的節(jié)點(diǎn)具有類似的結(jié)構(gòu)狸演,而不同的節(jié)點(diǎn)具有不同的結(jié)構(gòu)言蛇。在這種假設(shè)之上進(jìn)行逐層的比較,如果發(fā)現(xiàn)對(duì)應(yīng)的節(jié)點(diǎn)是不同的宵距,那就直接刪除舊的節(jié)點(diǎn)以及它所包含的所有子節(jié)點(diǎn)然后替換成新的節(jié)點(diǎn)腊尚。如果是相同的節(jié)點(diǎn),則只進(jìn)行屬性的更改满哪。
對(duì)于列表的diff算法稍有不同婿斥,因?yàn)榱斜硗ǔ>哂邢嗤慕Y(jié)構(gòu)劝篷,在對(duì)列表節(jié)點(diǎn)進(jìn)行刪除,插入民宿,排序的時(shí)候娇妓,單個(gè)節(jié)點(diǎn)的整體操作遠(yuǎn)比一個(gè)個(gè)對(duì)比一個(gè)個(gè)替換要好得多,所以在創(chuàng)建列表的時(shí)候需要設(shè)置key值活鹰,這樣react才能分清誰是誰哈恰。當(dāng)然不寫key值也可以,但這樣通常會(huì)報(bào)出警告志群,通知我們加上key值以提高react的性能着绷。
React組件是怎么來的
組件的創(chuàng)造方法為React.createClass() ——?jiǎng)?chuàng)造一個(gè)類,react系統(tǒng)內(nèi)部設(shè)計(jì)了一套類系統(tǒng)锌云,利用它來創(chuàng)造react組件蓬戚。但這并不是必須的,我們還可以用es6的class類來創(chuàng)造組件,這也是Facebook官方推薦的寫法宾抓。
這兩種寫法實(shí)現(xiàn)的功能一樣但是原理卻是不同子漩,es6的class類可以看作是構(gòu)造函數(shù)的一個(gè)語法糖,可以把它當(dāng)成構(gòu)造函數(shù)來看石洗,extends實(shí)現(xiàn)了類之間的繼承 —— 定義一個(gè)類Main 繼承React.Component所有的屬性和方法幢泼,組件的生命周期函數(shù)就是從這來的。constructor是構(gòu)造器讲衫,在實(shí)例化對(duì)象時(shí)調(diào)用缕棵,super調(diào)用了父類的constructor創(chuàng)造了父類的實(shí)例對(duì)象this,然后用子類的構(gòu)造函數(shù)進(jìn)行修改涉兽。這和es5的原型繼承是不同的招驴,原型繼承是先創(chuàng)造一個(gè)實(shí)例化對(duì)象this,然后再繼承父級(jí)的原型方法枷畏。了解了這些之后我們?cè)诳唇M件的時(shí)候就清楚很多别厘。
當(dāng)我們使用組件< Main />時(shí),其實(shí)是對(duì)Main類的實(shí)例化——new Main拥诡,只不過react對(duì)這個(gè)過程進(jìn)行了封裝触趴,讓它看起來更像是一個(gè)標(biāo)簽。
有三點(diǎn)值得注意:1渴肉、定義類名字的首字母必須大寫 2冗懦、因?yàn)閏lass變成了關(guān)鍵字,類選擇器需要用className來代替仇祭。 3披蕉、類和模塊內(nèi)部默認(rèn)使用嚴(yán)格模式,所以不需要用use strict指定運(yùn)行模式。
組件的生命周期
組件在初始化時(shí)會(huì)觸發(fā)5個(gè)鉤子函數(shù):
1没讲、getDefaultProps()
設(shè)置默認(rèn)的props承冰,也可以用dufaultProps設(shè)置組件的默認(rèn)屬性。
2食零、getInitialState()
在使用es6的class語法時(shí)是沒有這個(gè)鉤子函數(shù)的静袖,可以直接在constructor中定義this.state蟋字。此時(shí)可以訪問this.props肢娘。
3攒庵、componentWillMount()
組件初始化時(shí)只調(diào)用溪北,以后組件更新不調(diào)用笙蒙,整個(gè)生命周期只調(diào)用一次昔驱,此時(shí)可以修改state豁延。
4考廉、 render()
react最重要的步驟秘豹,創(chuàng)建虛擬dom,進(jìn)行diff算法昌粤,更新dom樹都在此進(jìn)行既绕。此時(shí)就不能更改state了。
5涮坐、componentDidMount()
組件渲染之后調(diào)用凄贩,可以通過this.getDOMNode()獲取和操作dom節(jié)點(diǎn),只調(diào)用一次袱讹。
在更新時(shí)也會(huì)觸發(fā)5個(gè)鉤子函數(shù):
6疲扎、componentWillReceivePorps(nextProps)
組件初始化時(shí)不調(diào)用,組件接受新的props時(shí)調(diào)用捷雕。
7椒丧、shouldComponentUpdate(nextProps, nextState)
react性能優(yōu)化非常重要的一環(huán)。組件接受新的state或者props時(shí)調(diào)用救巷,我們可以設(shè)置在此對(duì)比前后兩個(gè)props和state是否相同壶熏,如果相同則返回false阻止更新,因?yàn)橄嗤膶傩誀顟B(tài)一定會(huì)生成相同的dom樹浦译,這樣就不需要?jiǎng)?chuàng)造新的dom樹和舊的dom樹進(jìn)行diff算法對(duì)比久橙,節(jié)省大量性能,尤其是在dom結(jié)構(gòu)復(fù)雜的時(shí)候管怠。不過調(diào)用this.forceUpdate會(huì)跳過此步驟淆衷。
8、componentWillUpdata(nextProps, nextState)
組件初始化時(shí)不調(diào)用渤弛,只有在組件將要更新時(shí)才調(diào)用祝拯,此時(shí)可以修改state
9、render()
不多說
10、componentDidUpdate()
組件初始化時(shí)不調(diào)用佳头,組件更新完成后調(diào)用鹰贵,此時(shí)可以獲取dom節(jié)點(diǎn)。
還有一個(gè)卸載鉤子函數(shù)
11康嘉、componentWillUnmount()
組件將要卸載時(shí)調(diào)用碉输,一些事件監(jiān)聽和定時(shí)器需要在此時(shí)清除。
以上可以看出來react總共有10個(gè)周期函數(shù)(render重復(fù)一次)亭珍,這個(gè)10個(gè)函數(shù)可以滿足我們所有對(duì)組件操作的需求敷钾,利用的好可以提高開發(fā)效率和組件性能。
React-Router路由
Router就是React的一個(gè)組件肄梨,它并不會(huì)被渲染阻荒,只是一個(gè)創(chuàng)建內(nèi)部路由規(guī)則的配置對(duì)象,根據(jù)匹配的路由地址展現(xiàn)相應(yīng)的組件众羡。Route則對(duì)路由地址和組件進(jìn)行綁定侨赡,Route具有嵌套功能,表示路由地址的包涵關(guān)系粱侣,這和組件之間的嵌套并沒有直接聯(lián)系羊壹。Route可以向綁定的組件傳遞7個(gè)屬性:children,history齐婴,location舶掖,params,route尔店,routeParams眨攘,routes,每個(gè)屬性都包涵路由的相關(guān)的信息嚣州。比較常用的有children(以路由的包涵關(guān)系為區(qū)分的組件)鲫售,location(包括地址,參數(shù)该肴,地址切換方式情竹,key值,hash值)匀哄。react-router提供Link標(biāo)簽秦效,這只是對(duì)a標(biāo)簽的封裝,值得注意的是涎嚼,點(diǎn)擊鏈接進(jìn)行的跳轉(zhuǎn)并不是默認(rèn)的方式阱州,react-router阻止了a標(biāo)簽的默認(rèn)行為并用pushState進(jìn)行hash值的轉(zhuǎn)變。切換頁面的過程是在點(diǎn)擊Link標(biāo)簽或者后退前進(jìn)按鈕時(shí)法梯,會(huì)先發(fā)生url地址的轉(zhuǎn)變苔货,Router監(jiān)聽到地址的改變根據(jù)Route的path屬性匹配到對(duì)應(yīng)的組件犀概,將state值改成對(duì)應(yīng)的組件并調(diào)用setState觸發(fā)render函數(shù)重新渲染dom。
當(dāng)頁面比較多時(shí)夜惭,項(xiàng)目就會(huì)變得越來越大姻灶,尤其對(duì)于單頁面應(yīng)用來說,初次渲染的速度就會(huì)很慢诈茧,這時(shí)候就需要按需加載产喉,只有切換到頁面的時(shí)候才去加載對(duì)應(yīng)的js文件。react配合webpack進(jìn)行按需加載的方法很簡(jiǎn)單敢会,Route的component改為getComponent曾沈,組件用require.ensure的方式獲取,并在webpack中配置chunkFilename走触。
組件之間的通信
react推崇的是單向數(shù)據(jù)流,自上而下進(jìn)行數(shù)據(jù)的傳遞疤苹,但是由下而上或者不在一條數(shù)據(jù)流上的組件之間的通信就會(huì)變的復(fù)雜互广。解決通信問題的方法很多,如果只是父子級(jí)關(guān)系卧土,父級(jí)可以將一個(gè)回調(diào)函數(shù)當(dāng)作屬性傳遞給子級(jí)惫皱,子級(jí)可以直接調(diào)用函數(shù)從而和父級(jí)通信。
組件層級(jí)嵌套到比較深尤莺,可以使用上下文getChildContext來傳遞信息旅敷,這樣在不需要將函數(shù)一層層往下傳,任何一層的子級(jí)都可以通過this.context直接訪問颤霎。
兄弟關(guān)系的組件之間無法直接通信媳谁,它們只能利用同一層的上級(jí)作為中轉(zhuǎn)站。而如果兄弟組件都是最高層的組件友酱,為了能夠讓它們進(jìn)行通信晴音,必須在它們外層再套一層組件,這個(gè)外層的組件起著保存數(shù)據(jù)缔杉,傳遞信息的作用锤躁,這其實(shí)就是redux所做的事情。
組件之間的信息還可以通過全局事件來傳遞或详。不同頁面可以通過參數(shù)傳遞數(shù)據(jù)系羞,下個(gè)頁面可以用location.param來獲取。其實(shí)react本身很簡(jiǎn)單霸琴,難的在于如何優(yōu)雅高效的實(shí)現(xiàn)組件之間數(shù)據(jù)的交流椒振。
Redux
首先,redux并不是必須的梧乘,它的作用相當(dāng)于在頂層組件之上又加了一個(gè)組件杠人,作用是進(jìn)行邏輯運(yùn)算、儲(chǔ)存數(shù)據(jù)和實(shí)現(xiàn)組件尤其是頂層組件的通信。如果組件之間的交流不多嗡善,邏輯不復(fù)雜辑莫,只是單純的進(jìn)行視圖的渲染,這時(shí)候用回調(diào)罩引,context就行各吨,沒必要用redux,用了反而影響開發(fā)速度袁铐。但是如果組件交流特別頻繁揭蜒,邏輯很復(fù)雜,那redux的優(yōu)勢(shì)就特別明顯了剔桨。我第一次做react項(xiàng)目的時(shí)候并沒有用redux屉更,所有的邏輯都是在組件內(nèi)部實(shí)現(xiàn),當(dāng)時(shí)為了實(shí)現(xiàn)一個(gè)邏輯比較復(fù)雜的購物車洒缀,洋洋灑灑居然寫了800多行代碼瑰谜,回頭一看我自己都不知道寫的是啥,畫面太感人树绩。
先簡(jiǎn)單說一下redux和react是怎么配合的萨脑。react-redux提供了connect和Provider兩個(gè)好基友,它們一個(gè)將組件與redux關(guān)聯(lián)起來饺饭,一個(gè)將store傳給組件渤早。組件通過dispatch發(fā)出action,store根據(jù)action的type屬性調(diào)用對(duì)應(yīng)的reducer并傳入state和這個(gè)action瘫俊,reducer對(duì)state進(jìn)行處理并返回一個(gè)新的state放入store鹊杖,connect監(jiān)聽到store發(fā)生變化,調(diào)用setState更新組件扛芽,此時(shí)組件的props也就跟著變化仅淑。
流程是這個(gè)樣子的:
值得注意的是connect,Provider胸哥,mapStateToProps,mapDispatchToProps是react-redux提供的涯竟,redux本身和react沒有半毛錢關(guān)系,它只是數(shù)據(jù)處理中心空厌,沒有和react產(chǎn)生任何耦合庐船,是react-redux讓它們聯(lián)系在一起。
接下來具體分析一下嘲更,redux以及react-redux到底是怎么實(shí)現(xiàn)的筐钟。
先上一張圖
明顯比第一張要復(fù)雜,其實(shí)兩張圖說的是同一件事赋朦。從上而下慢慢分析:
先說說redux:
redux主要由三部分組成:store篓冲,reducer李破,action。
**store**是一個(gè)對(duì)象壹将,它有四個(gè)主要的方法:
**1嗤攻、dispatch:**
> 用于action的分發(fā)——在createStore中可以用middleware中間件對(duì)dispatch進(jìn)行改造,比如當(dāng)action傳入dispatch會(huì)立即觸發(fā)reducer诽俯,有些時(shí)候我們不希望它立即觸發(fā)妇菱,而是等待異步操作完成之后再觸發(fā),這時(shí)候用redux-thunk對(duì)dispatch進(jìn)行改造暴区,以前只能傳入一個(gè)對(duì)象闯团,改造完成后可以傳入一個(gè)函數(shù),在這個(gè)函數(shù)里我們手動(dòng)dispatch一個(gè)action對(duì)象仙粱,這個(gè)過程是可控的房交,就實(shí)現(xiàn)了異步。
**2伐割、subscribe:**
> 監(jiān)聽state的變化——這個(gè)函數(shù)在store調(diào)用dispatch時(shí)會(huì)注冊(cè)一個(gè)listener監(jiān)聽state變化候味,當(dāng)我們需要知道state是否變化時(shí)可以調(diào)用,它返回一個(gè)函數(shù)口猜,調(diào)用這個(gè)返回的函數(shù)可以注銷監(jiān)聽负溪。
let unsubscribe = store.subscribe(() => {console.log('state發(fā)生了變化')})
**3透揣、getState:**
> 獲取store中的state——當(dāng)我們用action觸發(fā)reducer改變了state時(shí)济炎,需要再拿到新的state里的數(shù)據(jù),畢竟數(shù)據(jù)才是我們想要的辐真。getState主要在兩個(gè)地方需要用到须尚,一是在dispatch拿到action后store需要用它來獲取state里的數(shù)據(jù),并把這個(gè)數(shù)據(jù)傳給reducer侍咱,這個(gè)過程是自動(dòng)執(zhí)行的耐床,二是在我們利用subscribe監(jiān)聽到state發(fā)生變化后調(diào)用它來獲取新的state數(shù)據(jù),如果做到這一步楔脯,說明我們已經(jīng)成功了撩轰。
**4、replaceReducer:**
> 替換reducer昧廷,改變state修改的邏輯堪嫂。
store可以通過createStore()方法創(chuàng)建,接受三個(gè)參數(shù)木柬,經(jīng)過combineReducers合并的reducer和state的初始狀態(tài)以及改變dispatch的中間件皆串,后兩個(gè)參數(shù)并不是必須的。store的主要作用是將action和reducer聯(lián)系起來并改變state眉枕。
**action:**
>action是一個(gè)對(duì)象恶复,其中type屬性是必須的怜森,同時(shí)可以傳入一些數(shù)據(jù)。action可以用actionCreactor進(jìn)行創(chuàng)造谤牡。dispatch就是把a(bǔ)ction對(duì)象發(fā)送出去副硅。
**reducer:**
>reducer是一個(gè)函數(shù),它接受一個(gè)state和一個(gè)action拓哟,根據(jù)action的type返回一個(gè)新的state想许。根據(jù)業(yè)務(wù)邏輯可以分為很多個(gè)reducer,然后通過combineReducers將它們合并断序,state樹中有很多對(duì)象流纹,每個(gè)state對(duì)象對(duì)應(yīng)一個(gè)reducer,state對(duì)象的名字可以在合并時(shí)定義违诗。
像這個(gè)樣子:
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
})
**combineReducers:**
>其實(shí)它也是一個(gè)reducer漱凝,它接受整個(gè)state和一個(gè)action,然后將整個(gè)state拆分發(fā)送給對(duì)應(yīng)的reducer進(jìn)行處理诸迟,所有的reducer會(huì)收到相同的action茸炒,不過它們會(huì)根據(jù)action的type進(jìn)行判斷,有這個(gè)type就進(jìn)行處理然后返回新的state阵苇,沒有就返回默認(rèn)值壁公,然后這些分散的state又會(huì)整合在一起返回一個(gè)新的state樹。
接下來分析一下整體的流程绅项,首先調(diào)用store.dispatch將action作為參數(shù)傳入紊册,同時(shí)用getState獲取當(dāng)前的狀態(tài)樹state并注冊(cè)subscribe的listener監(jiān)聽state變化,再調(diào)用combineReducers并將獲取的state和action傳入快耿。combineReducers會(huì)將傳入的state和action傳給所有reducer囊陡,reducer會(huì)根據(jù)state的key值獲取與自己對(duì)應(yīng)的state,并根據(jù)action的type返回新的state掀亥,觸發(fā)state樹的更新撞反,我們調(diào)用subscribe監(jiān)聽到state發(fā)生變化后用getState獲取新的state數(shù)據(jù)。
redux的state和react的state兩者完全沒有關(guān)系搪花,除了名字一樣遏片。
**上面分析了redux的主要功能,那么react-redux到底做了什么撮竿?**
React-Redux
如果只使用redux吮便,那么流程是這樣的:
> component --> dispatch(action) --> reducer --> subscribe --> getState --> component
用了react-redux之后流程是這樣的:
> component --> actionCreator(data) --> reducer --> component
store的三大功能:dispatch,subscribe倚聚,getState都不需要手動(dòng)來寫了线衫。react-redux幫我們做了這些,同時(shí)它提供了兩個(gè)好基友Provider和connect惑折。
**Provider**是一個(gè)組件授账,它接受store作為props枯跑,然后通過context往下傳,這樣react中任何組件都可以通過contex獲取store白热。也就意味著我們可以在任何一個(gè)組件里利用dispatch(action)來觸發(fā)reducer改變state敛助,并用subscribe監(jiān)聽state的變化,然后用getState獲取變化后的值屋确。但是并不推薦這樣做纳击,它會(huì)讓數(shù)據(jù)流變的混亂,過度的耦合也會(huì)影響組件的復(fù)用攻臀,維護(hù)起來也更麻煩焕数。
**connect --connect(mapStateToProps, mapDispatchToProps, mergeProps, options)**是一個(gè)函數(shù),它接受四個(gè)參數(shù)并且再返回一個(gè)函數(shù)--wrapWithConnect刨啸,wrapWithConnect接受一個(gè)組件作為參數(shù)wrapWithConnect(component)堡赔,它內(nèi)部定義一個(gè)新組件Connect(容器組件)并將傳入的組件(ui組件)作為Connect的子組件然后return出去。
所以它的完整寫法是這樣的:connect(mapStateToProps, mapDispatchToProps, mergeProps, options)(component)
**mapStateToProps(state, [ownProps]):**
>mapStateToProps 接受兩個(gè)參數(shù)设联,store的state和自定義的props善已,并返回一個(gè)新的對(duì)象,這個(gè)對(duì)象會(huì)作為props的一部分傳入ui組件离例。我們可以根據(jù)組件所需要的數(shù)據(jù)自定義返回一個(gè)對(duì)象换团。ownProps的變化也會(huì)觸發(fā)mapStateToProps
function mapStateToProps(state) {
return { todos: state.todos };
}
**mapDispatchToProps(dispatch, [ownProps]):**
> mapDispatchToProps如果是對(duì)象,那么會(huì)和store綁定作為props的一部分傳入ui組件宫蛆。如果是個(gè)函數(shù)艘包,它接受兩個(gè)參數(shù),bindActionCreators會(huì)將action和dispatch綁定并返回一個(gè)對(duì)象洒扎,這個(gè)對(duì)象會(huì)和ownProps一起作為props的一部分傳入ui組件辑甜。所以不論mapDispatchToProps是對(duì)象還是函數(shù)衰絮,它最終都會(huì)返回一個(gè)對(duì)象袍冷,如果是函數(shù),這個(gè)對(duì)象的key值是可以自定義的
unction mapDispatchToProps(dispatch) {
return {
todoActions: bindActionCreators(todoActionCreators, dispatch),
counterActions: bindActionCreators(counterActionCreators, dispatch)
};
}
mapDispatchToProps返回的對(duì)象其屬性其實(shí)就是一個(gè)個(gè)actionCreator猫牡,因?yàn)橐呀?jīng)和dispatch綁定胡诗,所以當(dāng)調(diào)用actionCreator時(shí)會(huì)立即發(fā)送action,而不用手動(dòng)dispatch淌友。ownProps的變化也會(huì)觸發(fā)mapDispatchToProps煌恢。
**mergeProps(stateProps, dispatchProps, ownProps):**
> 將mapStateToProps() 與 mapDispatchToProps()返回的對(duì)象和組件自身的props合并成新的props并傳入組件。默認(rèn)返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的結(jié)果震庭。
**options:**
> pure = true 表示Connect容器組件將在shouldComponentUpdate中對(duì)store的state和ownProps進(jìn)行淺對(duì)比瑰抵,判斷是否發(fā)生變化,優(yōu)化性能器联。為false則不對(duì)比二汛。
其實(shí)connect函數(shù)并沒有做什么婿崭,大部分的邏輯都是在它返回的wrapWithConnect函數(shù)內(nèi)實(shí)現(xiàn)的,確切的說是在wrapWithConnect內(nèi)定義的Connect組件里實(shí)現(xiàn)的肴颊。
下面是一個(gè)完整的 react --> redux --> react 流程:
一氓栈、Provider組件接受redux的store作為props,然后通過context往下傳婿着。
二授瘦、connect函數(shù)在初始化的時(shí)候會(huì)將mapDispatchToProps對(duì)象綁定到store,如果mapDispatchToProps是函數(shù)則在Connect組件獲得store后竟宋,根據(jù)傳入的store.dispatch和action通過bindActionCreators進(jìn)行綁定提完,再將返回的對(duì)象綁定到store,connect函數(shù)會(huì)返回一個(gè)wrapWithConnect函數(shù)丘侠,同時(shí)wrapWithConnect會(huì)被調(diào)用且傳入一個(gè)ui組件氯葬,wrapWithConnect內(nèi)部使用class Connect extends Component定義了一個(gè)Connect組件,傳入的ui組件就是Connect的子組件婉陷,然后Connect組件會(huì)通過context獲得store帚称,并通過store.getState獲得完整的state對(duì)象,將state傳入mapStateToProps返回stateProps對(duì)象秽澳、mapDispatchToProps對(duì)象或mapDispatchToProps函數(shù)會(huì)返回一個(gè)dispatchProps對(duì)象闯睹,stateProps、dispatchProps以及Connect組件的props三者通過Object.assign()担神,或者mergeProps合并為props傳入ui組件楼吃。然后在ComponentDidMount中調(diào)用store.subscribe,注冊(cè)了一個(gè)回調(diào)函數(shù)handleChange監(jiān)聽state的變化妄讯。
三孩锡、此時(shí)ui組件就可以在props中找到actionCreator,當(dāng)我們調(diào)用actionCreator時(shí)會(huì)自動(dòng)調(diào)用dispatch亥贸,在dispatch中會(huì)調(diào)用getState獲取整個(gè)state躬窜,同時(shí)注冊(cè)一個(gè)listener監(jiān)聽state的變化,store將獲得的state和action傳給combineReducers炕置,combineReducers會(huì)將state依據(jù)state的key值分別傳給子reducer荣挨,并將action傳給全部子reducer,reducer會(huì)被依次執(zhí)行進(jìn)行action.type的判斷朴摊,如果有則返回一個(gè)新的state默垄,如果沒有則返回默認(rèn)。combineReducers再次將子reducer返回的單個(gè)state進(jìn)行合并成一個(gè)新的完整的state甚纲。此時(shí)state發(fā)生了變化口锭。Connect組件中調(diào)用的subscribe會(huì)監(jiān)聽到state發(fā)生了變化,然后調(diào)用handleChange函數(shù)介杆,handleChange函數(shù)內(nèi)部首先調(diào)用getState獲取新的state值并對(duì)新舊兩個(gè)state進(jìn)行淺對(duì)比鹃操,如果相同直接return况既,如果不同則調(diào)用mapStateToProps獲取stateProps并將新舊兩個(gè)stateProps進(jìn)行淺對(duì)比,如果相同组民,直接return結(jié)束棒仍,不進(jìn)行后續(xù)操作。如果不相同則調(diào)用this.setState()觸發(fā)Connect組件的更新臭胜,傳入ui組件莫其,觸發(fā)ui組件的更新,此時(shí)ui組件獲得新的props耸三,react --> redux --> react 的一次流程結(jié)束乱陡。
**上面的有點(diǎn)復(fù)雜,簡(jiǎn)化版的流程是:**
一仪壮、Provider組件接受redux的store作為props憨颠,然后通過context往下傳。
二积锅、connect函數(shù)收到Provider傳出的store爽彤,然后接受三個(gè)參數(shù)mapStateToProps,mapDispatchToProps和組件缚陷,并將state和actionCreator以props傳入組件适篙,這時(shí)組件就可以調(diào)用actionCreator函數(shù)來觸發(fā)reducer函數(shù)返回新的state,connect監(jiān)聽到state變化調(diào)用setState更新組件并將新的state傳入組件箫爷。
connect可以寫的非常簡(jiǎn)潔嚷节,mapStateToProps,mapDispatchToProps只不過是傳入的回調(diào)函數(shù)虎锚,connect函數(shù)在必要的時(shí)候會(huì)調(diào)用它們硫痰,名字不是固定的,甚至可以不寫名字窜护。
簡(jiǎn)化版本:
connect(state => state, action)(Component);
項(xiàng)目搭建
上面說了react效斑,react-router和redux的知識(shí)點(diǎn)。但是怎么樣將它們整合起來柄慰,搭建一個(gè)完整的項(xiàng)目鳍悠。
1税娜、先引用 react.js坐搔,redux,react-router 等基本文件敬矩,建議用npm安裝概行,直接在文件中引用。
2弧岳、從 react.js凳忙,redux业踏,react-router 中引入所需要的對(duì)象和方法。
import React, {Component, PropTypes} from 'react';
import ReactDOM, {render} from 'react-dom';
import {Provider, connect} from 'react-redux';
import {createStore, combineReducers, applyMiddleware} from 'redux';
import { Router, Route, Redirect, IndexRoute, browserHistory, hashHistory } from 'react-router';
3涧卵、根據(jù)需求創(chuàng)建頂層ui組件勤家,每個(gè)頂層ui組件對(duì)應(yīng)一個(gè)頁面。
4柳恐、創(chuàng)建actionCreators和reducers伐脖,并用combineReducers將所有的reducer合并成一個(gè)大的reduer。利用createStore創(chuàng)建store并引入combineReducers和applyMiddleware乐设。
5讼庇、利用connect將actionCreator,reuder和頂層的ui組件進(jìn)行關(guān)聯(lián)并返回一個(gè)新的組件近尚。
6蠕啄、利用connect返回的新的組件配合react-router進(jìn)行路由的部署,返回一個(gè)路由組件Router戈锻。
7歼跟、將Router放入最頂層組件Provider,引入store作為Provider的屬性格遭。
8嘹承、調(diào)用render渲染Provider組件且放入頁面的標(biāo)簽中。
可以看到頂層的ui組件其實(shí)被套了四層組件如庭,Provider叹卷,Router,Route坪它,Connect骤竹,這四個(gè)組件并不會(huì)在視圖上改變r(jià)eact,它們只是功能性的往毡。
通常我們?cè)陧攲拥膗i組件打印props時(shí)可以看到一堆屬性:
上圖的頂層ui組件屬性總共有18個(gè)蒙揣,如果剛剛接觸react,可能對(duì)這些屬性怎么來的感到困惑开瞭,其實(shí)這些屬性來自五個(gè)地方:
組件自定義屬性1個(gè)懒震,actionCreator返回的對(duì)象6個(gè),reducer返回的state4個(gè)嗤详,Connect組件屬性0個(gè)个扰,以及Router注入的屬性7個(gè)。
項(xiàng)目源碼地址:https://github.com/bailicangdu/pxq