React+Redux基礎(chǔ)

技術(shù)棧:

react + redux + webpack + react-router + ES6/7/8 + immutable

運(yùn)行項(xiàng)目(nodejs 6.0+)

 git clone https://github.com/bailicangdu/react-pxq.git

 cd react-pxq

 npm i
  
 npm start

 npm run build (發(fā)布)

做React需要會(huì)什么立莉?

react的功能其實(shí)很單一棚点,主要負(fù)責(zé)渲染的功能逛绵,現(xiàn)有的框架虱黄,比如angular是一個(gè)大而全的框架澳窑,用了angular幾乎就不需要用其他工具輔助配合磅网,但是react不一樣搭独,他只負(fù)責(zé)ui渲染玖院,想要做好一個(gè)項(xiàng)目违帆,往往需要其他庫(kù)和工具的配合,比如用redux來(lái)管理數(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每次訪問(wèn)DOM,都要途徑這座橋芜抒,并交納“過(guò)橋費(fèi)”,訪問(wèn)DOM的次數(shù)越多珍策,費(fèi)用也就越高。 因此挽绩,推薦的做法是盡量減少過(guò)橋的次數(shù)膛壹,努力待在ECMAScript島上。因?yàn)檫@個(gè)原因react的虛擬dom就顯得難能可貴了唉堪,它創(chuàng)造了虛擬dom并且將它們儲(chǔ)存起來(lái)模聋,每當(dāng)狀態(tài)發(fā)生變化的時(shí)候就會(huì)創(chuàng)造新的虛擬節(jié)點(diǎn)和以前的進(jìn)行對(duì)比,讓變化的部分進(jìn)行渲染唠亚。整個(gè)過(guò)程沒有對(duì)dom進(jìn)行獲取和操作链方,只有一個(gè)渲染的過(guò)程,所以react說(shuō)是一個(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ù)變化誰(shuí)來(lái)管理?然而這并不是react所要關(guān)心的事情丙挽,它只負(fù)責(zé)ui的渲染肺孵。與其他框架監(jiān)聽數(shù)據(jù)動(dòng)態(tài)改變dom不同匀借,react采用setState來(lái)控制視圖的更新。setState會(huì)自動(dòng)調(diào)用render函數(shù)平窘,觸發(fā)視圖的重新渲染吓肋,如果僅僅只是state數(shù)據(jù)的變化而沒有調(diào)用setState,并不會(huì)觸發(fā)更新瑰艘。 組件就是擁有獨(dú)立功能的視圖模塊是鬼,許多小的組件組成一個(gè)大的組件,整個(gè)頁(yè)面就是由一個(gè)個(gè)組件組合而成紫新。它的好處是利于重復(fù)利用和維護(hù)屑咳。

React的 Diff算法

react的diff算法用在什么地方呢?當(dāng)組件更新的時(shí)候弊琴,react會(huì)創(chuàng)建一個(gè)新的虛擬dom樹并且會(huì)和之前儲(chǔ)存的dom樹進(jìn)行比較,這個(gè)比較多過(guò)程就用到了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才能分清誰(shuí)是誰(shuí)。當(dāng)然不寫key值也可以夷磕,但這樣通常會(huì)報(bào)出警告履肃,通知我們加上key值以提高react的性能。

React組件是怎么來(lái)的

組件的創(chuàng)造方法為React.createClass() ——?jiǎng)?chuàng)造一個(gè)類坐桩,react系統(tǒng)內(nèi)部設(shè)計(jì)了一套類系統(tǒng)尺棋,利用它來(lái)創(chuàng)造react組件。但這并不是必須的撕攒,我們還可以用es6的class類來(lái)創(chuàng)造組件,這也是Facebook官方推薦的寫法陡鹃。

class Main extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            productList:[],
            params:'',
        }
    }
}

這兩種寫法實(shí)現(xiàn)的功能一樣但是原理卻是不同烘浦,es6的class類可以看作是構(gòu)造函數(shù)的一個(gè)語(yǔ)法糖,可以把它當(dāng)成構(gòu)造函數(shù)來(lái)看萍鲸,extends實(shí)現(xiàn)了類之間的繼承 —— 定義一個(gè)類Main 繼承React.Component所有的屬性和方法闷叉,組件的生命周期函數(shù)就是從這來(lái)的。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蜜猾,只不過(guò)react對(duì)這個(gè)過(guò)程進(jìn)行了封裝秀菱,讓它看起來(lái)更像是一個(gè)標(biāo)簽。

有三點(diǎn)值得注意:1蹭睡、定義類名字的首字母必須大寫 2衍菱、因?yàn)閏lass變成了關(guān)鍵字,類選擇器需要用className來(lái)代替肩豁。 3脊串、類和模塊內(nèi)部默認(rèn)使用嚴(yán)格模式,所以不需要用use strict指定運(yùn)行模式清钥。

組件的生命周期

image

組件在初始化時(shí)會(huì)觸發(fā)5個(gè)鉤子函數(shù):

1琼锋、getDefaultProps()

設(shè)置默認(rèn)的props,也可以用dufaultProps設(shè)置組件的默認(rèn)屬性祟昭。

2斩例、getInitialState()

在使用es6的class語(yǔ)法時(shí)是沒有這個(gè)鉤子函數(shù)的,可以直接在constructor中定義this.state从橘。此時(shí)可以訪問(wèn)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)用袁稽,可以通過(guò)this.getDOMNode()獲取和操作dom節(jié)點(diǎn),只調(diào)用一次擒抛。

在更新時(shí)也會(huì)觸發(fā)5個(gè)鉤子函數(shù):

6推汽、componentWillReceiveProps(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í)候早直。不過(guò)調(diào)用this.forceUpdate會(huì)跳過(guò)此步驟。

8市框、componentWillUpdate(nextProps, nextState)

組件初始化時(shí)不調(diào)用霞扬,只有在組件將要更新時(shí)才調(diào)用,此時(shí)可以修改state

9枫振、render()

不多說(shuō)

10喻圃、componentDidUpdate()

組件初始化時(shí)不調(diào)用,組件更新完成后調(diào)用粪滤,此時(shí)可以獲取dom節(jié)點(diǎn)斧拍。

還有一個(gè)卸載鉤子函數(shù)

11、componentWillUnmount()

組件將要卸載時(shí)調(diào)用杖小,一些事件監(jiān)聽和定時(shí)器需要在此時(shí)清除肆汹。

以上可以看出來(lái)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)變祖秒。切換頁(yè)面的過(guò)程是在點(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)頁(yè)面比較多時(shí),項(xiàng)目就會(huì)變得越來(lái)越大抬纸,尤其對(duì)于單頁(yè)面應(yīng)用來(lái)說(shuō)咙俩,初次渲染的速度就會(huì)很慢,這時(shí)候就需要按需加載湿故,只有切換到頁(yè)面的時(shí)候才去加載對(duì)應(yīng)的js文件阿趁。react配合webpack進(jìn)行按需加載的方法很簡(jiǎn)單,Route的component改為getComponent坛猪,組件用require.ensure的方式獲取歌焦,并在webpack中配置chunkFilename。

const chooseProducts = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../Component/chooseProducts').default)
    },'chooseProducts')
}

const helpCenter = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../Component/helpCenter').default)
    },'helpCenter')
}

const saleRecord = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('../Component/saleRecord').default)
    },'saleRecord')
}

const RouteConfig = (
    <Router history={history}>
        <Route path="/" component={Roots}>
            <IndexRoute component={index} />//首頁(yè)
            <Route path="index" component={index} />
            <Route path="helpCenter" getComponent={helpCenter} />//幫助中心
            <Route path="saleRecord" getComponent={saleRecord} />//銷售記錄
            <Redirect from='*' to='/'  />
        </Route>
    </Router>
);

組件之間的通信

react推崇的是單向數(shù)據(jù)流砚哆,自上而下進(jìn)行數(shù)據(jù)的傳遞独撇,但是由下而上或者不在一條數(shù)據(jù)流上的組件之間的通信就會(huì)變的復(fù)雜屑墨。解決通信問(wèn)題的方法很多,如果只是父子級(jí)關(guān)系纷铣,父級(jí)可以將一個(gè)回調(diào)函數(shù)當(dāng)作屬性傳遞給子級(jí)卵史,子級(jí)可以直接調(diào)用函數(shù)從而和父級(jí)通信。

組件層級(jí)嵌套到比較深搜立,可以使用上下文getChildContext來(lái)傳遞信息以躯,這樣在不需要將函數(shù)一層層往下傳,任何一層的子級(jí)都可以通過(guò)this.context直接訪問(wèn)啄踊。

兄弟關(guān)系的組件之間無(wú)法直接通信忧设,它們只能利用同一層的上級(jí)作為中轉(zhuǎn)站。而如果兄弟組件都是最高層的組件颠通,為了能夠讓它們進(jìn)行通信址晕,必須在它們外層再套一層組件,這個(gè)外層的組件起著保存數(shù)據(jù)顿锰,傳遞信息的作用谨垃,這其實(shí)就是redux所做的事情。

組件之間的信息還可以通過(guò)全局事件來(lái)傳遞硼控。不同頁(yè)面可以通過(guò)參數(shù)傳遞數(shù)據(jù)刘陶,下個(gè)頁(yè)面可以用location.param來(lái)獲取。其實(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ì)就特別明顯了。

先簡(jiǎn)單說(shuō)一下redux和react是怎么配合的吠撮。react-redux提供了connect和Provider兩個(gè)好基友尊惰,它們一個(gè)將組件與redux關(guān)聯(lián)起來(lái),一個(gè)將store傳給組件。組件通過(guò)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è)樣子的:

image

值得注意的是connect全庸,Provider秀仲,mapStateToProps,mapDispatchToProps是react-redux提供的,redux本身和react沒有半毛錢關(guān)系壶笼,它只是數(shù)據(jù)處理中心神僵,沒有和react產(chǎn)生任何耦合,是react-redux讓它們聯(lián)系在一起拌消。

接下來(lái)具體分析一下挑豌,redux以及react-redux到底是怎么實(shí)現(xiàn)的。

先上一張圖

image

明顯比第一張要復(fù)雜墩崩,其實(shí)兩張圖說(shuō)的是同一件事氓英。從上而下慢慢分析:

先說(shuō)說(shuō)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è)過(guò)程是可控的棒厘,就實(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需要用它來(lái)獲取state里的數(shù)據(jù),并把這個(gè)數(shù)據(jù)傳給reducer蒙保,這個(gè)過(guò)程是自動(dòng)執(zhí)行的辕棚,二是在我們利用subscribe監(jiān)聽到state發(fā)生變化后調(diào)用它來(lái)獲取新的state數(shù)據(jù),如果做到這一步邓厕,說(shuō)明我們已經(jīng)成功了逝嚎。

4、replaceReducer:

替換reducer详恼,改變state修改的邏輯补君。

store可以通過(guò)createStore()方法創(chuàng)建,接受三個(gè)參數(shù)昧互,經(jīng)過(guò)combineReducers合并的reducer和state的初始狀態(tài)以及改變dispatch的中間件挽铁,后兩個(gè)參數(shù)并不是必須的。store的主要作用是將action和reducer聯(lián)系起來(lái)并改變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劲厌,然后通過(guò)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椿访,不過(guò)它們會(huì)根據(jù)action的type進(jìn)行判斷乌企,有這個(gè)type就進(jìn)行處理然后返回新的state,沒有就返回默認(rèn)值成玫,然后這些分散的state又會(huì)整合在一起返回一個(gè)新的state樹加酵。

接下來(lái)分析一下整體的流程拳喻,首先調(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,并根據(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)來(lái)寫了巾兆。react-redux幫我們做了這些,同時(shí)它提供了兩個(gè)好基友Provider和connect虎囚。

Provider是一個(gè)組件角塑,它接受store作為props,然后通過(guò)context往下傳溜宽,這樣react中任何組件都可以通過(guò)context獲取store吉拳。也就意味著我們可以在任何一個(gè)組件里利用dispatch(action)來(lái)觸發(fā)reducer改變state,并用subscribe監(jiān)聽state的變化适揉,然后用getState獲取變化后的值留攒。但是并不推薦這樣做,它會(huì)讓數(shù)據(jù)流變的混亂嫉嘀,過(guò)度的耦合也會(huì)影響組件的復(fù)用炼邀,維護(hù)起來(lái)也更麻煩。

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值是可以自定義的

function 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)的,確切的說(shuō)是在wrapWithConnect內(nèi)定義的Connect組件里實(shí)現(xiàn)的见咒。

下面是一個(gè)完整的 react --> redux --> react 流程:

一偿衰、Provider組件接受redux的store作為props,然后通過(guò)context往下傳改览。

二下翎、connect函數(shù)在初始化的時(shí)候會(huì)將mapDispatchToProps對(duì)象綁定到store,如果mapDispatchToProps是函數(shù)則在Connect組件獲得store后宝当,根據(jù)傳入的store.dispatch和action通過(guò)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ì)通過(guò)context獲得store虏辫,并通過(guò)store.getState獲得完整的state對(duì)象,將state傳入mapStateToProps返回stateProps對(duì)象锈拨、mapDispatchToProps對(duì)象或mapDispatchToProps函數(shù)會(huì)返回一個(gè)dispatchProps對(duì)象砌庄,stateProps、dispatchProps以及Connect組件的props三者通過(guò)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ā)生了變化留潦。dispatch在state返回新的值之后會(huì)調(diào)用所有注冊(cè)的listener函數(shù)其中包括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荣刑,然后通過(guò)context往下傳。

二伦乔、connect函數(shù)收到Provider傳出的store厉亏,然后接受三個(gè)參數(shù)mapStateToProps,mapDispatchToProps和組件烈和,并將state和actionCreator以props傳入組件爱只,這時(shí)組件就可以調(diào)用actionCreator函數(shù)來(lái)觸發(fā)reducer函數(shù)返回新的state,connect監(jiān)聽到state變化調(diào)用setState更新組件并將新的state傳入組件招刹。

connect可以寫的非常簡(jiǎn)潔恬试,mapStateToProps窝趣,mapDispatchToProps只不過(guò)是傳入的回調(diào)函數(shù),connect函數(shù)在必要的時(shí)候會(huì)調(diào)用它們训柴,名字不是固定的哑舒,甚至可以不寫名字。

簡(jiǎn)化版本:

connect(state => state, action)(Component);

項(xiàng)目搭建

上面說(shuō)了react幻馁,react-router和redux的知識(shí)點(diǎn)洗鸵。但是怎么樣將它們整合起來(lái),搭建一個(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è)頁(yè)面踊东。

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組件且放入頁(yè)面的標(biāo)簽中滑凉。

可以看到頂層的ui組件其實(shí)被套了四層組件,Provider,Router畅姊,Route咒钟,Connect,這四個(gè)組件并不會(huì)在視圖上改變r(jià)eact若未,它們只是功能性的朱嘴。

通常我們?cè)陧攲拥膗i組件打印props時(shí)可以看到一堆屬性:

image

上圖的頂層ui組件屬性總共有18個(gè),如果剛剛接觸react陨瘩,可能對(duì)這些屬性怎么來(lái)的感到困惑,其實(shí)這些屬性來(lái)自五個(gè)地方:

組件自定義屬性1個(gè)级乍,actionCreator返回的對(duì)象6個(gè)舌劳,reducer返回的state4個(gè),Connect組件屬性0個(gè)玫荣,以及Router注入的屬性7個(gè)甚淡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市捅厂,隨后出現(xiàn)的幾起案子贯卦,更是在濱河造成了極大的恐慌,老刑警劉巖焙贷,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撵割,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡辙芍,警方通過(guò)查閱死者的電腦和手機(jī)啡彬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)故硅,“玉大人庶灿,你說(shuō)我怎么就攤上這事〕孕疲” “怎么了往踢?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)徘层。 經(jīng)常有香客問(wèn)我峻呕,道長(zhǎng),這世上最難降的妖魔是什么趣效? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任山上,我火速辦了婚禮,結(jié)果婚禮上英支,老公的妹妹穿的比我還像新娘佩憾。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布妄帘。 她就那樣靜靜地躺著楞黄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抡驼。 梳的紋絲不亂的頭發(fā)上鬼廓,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音致盟,去河邊找鬼碎税。 笑死,一個(gè)胖子當(dāng)著我的面吹牛馏锡,可吹牛的內(nèi)容都是我干的雷蹂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼杯道,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼匪煌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起党巾,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤萎庭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后齿拂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驳规,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年署海,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了达舒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叹侄,死狀恐怖巩搏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情趾代,我是刑警寧澤贯底,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站撒强,受9級(jí)特大地震影響禽捆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜飘哨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一胚想、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芽隆,春花似錦浊服、人聲如沸统屈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)愁憔。三九已至,卻和暖如春孽拷,著一層夾襖步出監(jiān)牢的瞬間吨掌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工脓恕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留膜宋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓炼幔,卻偏偏與公主長(zhǎng)得像秋茫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子江掩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容