有一段解釋很透徹: 用腳本進行DOM操做的代價很昂貴。有個貼切的比喻,把DOM和JavaScript各自想象爲一個島嶼,它們之間用收費橋樑鏈接,js每次訪問DOM,都要途徑這座橋,並交納「過橋費」,訪問DOM的次數越多,費用也就越高。 所以,推薦的作法是儘可能減小過橋的次數,努力待在ECMAScript島上。 由於這個緣由react的虛擬dom就顯得難能難得了,它創造了虛擬dom而且將它們儲存起來,每當狀態發生變化的時候就會創造新的虛擬節點和之前的進行對比(Diff算法),讓變化的部分進行渲染。整個過程沒有對dom進行獲取和操做,只有一個渲染的過程,因此react說是一個ui框架。javascript
react的一個組件很明顯的由dom視圖和state數據組成,兩個部分涇渭分明。 state是數據中心,它的狀態決定着視圖的狀態。這時候發現彷佛和咱們一直推崇的MVC開發模式有點區別,沒了Controller控制器,那用戶交互怎麼處理,數據變化誰來管理?然而這並非react所要關心的事情,它只負責ui的渲染。與其餘框架監聽數據動態改變dom不一樣,react採用setState來控制視圖的更新。setState會自動調用render函數,觸發視圖的從新渲染,若是僅僅只是state數據的變化而沒有調用setState,並不會觸發更新。 組件就是擁有獨立功能的視圖模塊,許多小的組件組成一個大的組件,整個頁面就是由一個個組件組合而成。它的好處是利於重複利用和維護。java
eact的diff算法用在什麼地方呢?當組件更新的時候,react會建立一個新的虛擬dom樹而且會和以前儲存的dom樹進行比較,這個比較多過程就用到了diff算法,因此組件初始化的時候是用不到的。react提出了一種假設,相同的節點具備相似的結構,而不一樣的節點具備不一樣的結構。在這種假設之上進行逐層的比較,若是發現對應的節點是不一樣的,那就直接刪除舊的節點以及它所包含的全部子節點而後替換成新的節點。若是是相同的節點,則只進行屬性的更改。react
對於列表的diff算法稍有不一樣,由於列表一般具備相同的結構,在對列表節點進行刪除,插入,排序的時候,單個節點的總體操做遠比一個個對比一個個替換要好得多,因此在建立列表的時候須要設置key值,這樣react才能分清誰是誰。固然不寫key值也能夠,但這樣一般會報出警告,通知咱們加上key值以提升react的性能。 webpack
組件的創造方法爲React.createClass() ——創造一個類,react系統內部設計了一套類系統,利用它來創造react組件。但這並非必須的,咱們還能夠用es6的class類來創造組件,這也是Facebook官方推薦的寫法。git
class MyComponent extends React.Component{
constructor(props){
super(props);
this.state ={
params:'',
MyList:[]
}
}
複製代碼
這兩種寫法實現的功能同樣可是原理倒是不一樣,es6的class類能夠看做是構造函數的一個語法糖,能夠把它當成構造函數來看,extends實現了類之間的繼承 —— 定義一個類MyComponent繼承React.Component全部的屬性和方法,組件的生命週期函數就是從這來的。 constructor是構造器,在實例化對象時調用,super調用了父類的constructor創造了父類的實例對象this,而後用子類的構造函數進行修改。這和es5的原型繼承是不一樣的,原型繼承是先創造一個實例化對象this,而後再繼承父級的原型方法。 當咱們使用組件時,實際上是對MyComponent類的實例化,只不過react對這個過程進行了封裝,使其看起來像一個標籤。 props訪問父組件的屬性 this.props.children 訪問組件的孩子節點 能夠用React.Children.map()遍歷this.props.childrenes6
1.componentWillReceiveProps(nextProps) :組件初始化時不調用,組件接受新的props時調用。 2. shouldComponentUpdate(nextProps,nextState) :react性能優化很是重要的一環。組件接受新的state或者props時調用,咱們能夠設置在此對比先後兩個props和state是否相同,若是相同則返回false阻止更新,由於相同的屬性狀態必定會生成相同的dom樹,這樣就不須要創造新的dom樹和舊的dom樹進行diff算法對比,節省大量性能,尤爲是在dom結構複雜的時候。不過調用this.forceUpdate會跳過此步驟。 3. componentWillUpdate(nextProps,nextState) :組件初始化時不調用,只有在組件將要更新時才調用,此時能夠修改state 4. render() 5. componentDidUpdate() :組件初始化時不調用,組件更新完成後調用,此時能夠獲取dom節點。github
Router就是react的一個組件,並不會被渲染,只是一個建立內部路由規則的配置對象,根據匹配的路由地址展示相應的組件。 Router則對路由地址和組件進行綁定,Router具備嵌套功能,表示路由地址的包含關係,這和組件的嵌套沒有直接聯繫。 Router能夠向綁定的組件傳遞7個屬性:children,history,location,params,route,routeParams,routes,每一個屬性都包涵路由的相關的信息。比較經常使用的有children(以路由的包涵關係爲區分的組件),location(包括地址,參數,地址切換方式,key值,hash值)。react-router提供Link標籤,這只是對a標籤的封裝,值得注意的是,點擊連接進行的跳轉並非默認的方式,react-router阻止了a標籤的默認行爲並用pushState進行hash值的轉變。切換頁面的過程是在點擊Link標籤或者後退前進按鈕時,會先發生url地址的轉變,Router監聽到地址的改變根據Route的path屬性匹配到對應的組件,將state值改爲對應的組件並調用setState觸發render函數從新渲染dom。 當頁面比較多時,項目就會變得愈來愈大,尤爲對於單頁面應用來講,初次渲染的速度就會很慢,這時候就須要按需加載,只有切換到頁面的時候纔去加載對應的js文件。react配合webpack進行按需加載的方法很簡單,Route的component改成getComponent,組件用require.ensure的方式獲取,並在webpack中配置chunkFilename。web
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} />//首頁
<Route path="index" component={index} />
<Route path="helpCenter" getComponent={helpCenter} />//幫助中心
<Route path="saleRecord" getComponent={saleRecord} />//銷售記錄
<Redirect from='*' to='/' />
</Route>
</Router>
);
複製代碼
react推崇的是單向數據流,自上而下進行數據的傳遞,可是由下而上或者不在一條數據流上的組件之間的通訊就會變的複雜。解決通訊問題的方法不少,若是隻是父子級關係,父級能夠將一個回調函數看成屬性傳遞給子級,子級能夠直接調用函數從而和父級通訊。算法
組件層級嵌套到比較深,可使用上下文getChildContext來傳遞信息,這樣在不須要將函數一層層往下傳,任何一層的子級均可以經過this.context直接訪問。npm
兄弟關係的組件之間沒法直接通訊,它們只能利用同一層的上級做爲中轉站。而若是兄弟組件都是最高層的組件,爲了可以讓它們進行通訊,必須在它們外層再套一層組件,這個外層的組件起着保存數據,傳遞信息的做用,這其實就是redux所作的事情。
組件之間的信息還能夠經過全局事件來傳遞。不一樣頁面能夠經過參數傳遞數據,下個頁面能夠用location.param來獲取。其實react自己很簡單,難的在於如何優雅高效的實現組件之間數據的交流。
首先,redux並非必須的,它的做用至關於在頂層組件之上又加了一個組件,做用是進行邏輯運算、儲存數據和實現組件尤爲是頂層組件的通訊。若是組件之間的交流很少,邏輯不復雜,只是單純的進行視圖的渲染,這時候用回調,context就行,不必用redux,用了反而影響開發速度。可是若是組件交流特別頻繁,邏輯很複雜,那redux的優點就特別明顯了。 Redux 的react綁定庫是基於 容器組件和展現組件相分離 的開發思想。
react-redux提供了connect和Provider兩個好基友,它們一個將組件與redux關聯起來,一個將store傳給組件。組件經過dispatch發出action,store根據action的type屬性調用對應的reducer並傳入state和這個action,reducer對state進行處理並返回一個新的state放入store,connect監聽到store發生變化,調用setState更新組件,此時組件的props也就跟着變化。 流程以下:
store是一個對象,有四個主要方法:
用於action的分發——在createStore中能夠用middleware中間件對dispatch進行改造,好比當action傳入dispatch會當即觸發reducer,有些時候咱們不但願它當即觸發,而是等待異步操做完成以後再觸發,這時候用redux-thunk對dispatch進行改造,之前只能傳入一個對象,改造完成後能夠傳入一個函數,在這個函數裏咱們手動dispatch一個action對象,這個過程是可控的,就實現了異步。
監聽state的變化——這個函數在store調用dispatch時會註冊一個listener監聽state變化,當咱們須要知道state是否變化時能夠調用,它返回一個函數,調用這個返回的函數能夠註銷監聽
獲取store中的state——當咱們用action觸發reducer改變了state時,須要再拿到新的state裏的數據,畢竟數據纔是咱們想要的。getState主要在兩個地方須要用到,一是在dispatch拿到action後store須要用它來獲取state裏的數據,並把這個數據傳給reducer,這個過程是自動執行的,二是在咱們利用subscribe監聽到state發生變化後調用它來獲取新的state數據,若是作到這一步,說明咱們已經成功了
替換reducer,改變state修改的邏輯
action是一個對象,其中type屬性是必須的,同時能夠傳入一些數據。action能夠用actionCreator進行創造。dispatch就是把action對象發送出去。
官網上是這麼定義的
Actions describe the fact that something happened, but don't specify how the application's state changes in response. This is the job of reducers.
reducer是一個函數,它接受一個state和一個action,根據action的type返回一個新的state。根據業務邏輯能夠分爲不少個reducer,而後經過combineReducers將它們合併,state樹中有不少對象,每一個state對象對應一個reducer,state對象的名字能夠在合併時定義。
const reducer =combineReducers({
a:doSometingWithA,
b:processB,
c:c
})
複製代碼
其實它也是一個reducer,它接受整個state和一個action,而後將整個state拆分發送給對應的reducer進行處理,全部的reducer會收到相同的action,不過它們會根據action的type進行判斷,有這個type就進行處理而後返回新的state,沒有就返回默認值,而後這些分散的state又會整合在一塊兒返回一個新的state樹。
首先調用store.dispatch將action做爲參數傳入,同時用調用getState獲取當前的狀態樹state並註冊subscribe的listener監聽state變化,再調用combineReducers並將獲取的state和action傳入。combineReducers會將傳入的state和action傳給全部reducer,reducer會根據state的key值獲取與本身對應的state,並根據action的type返回新的state,觸發state樹的更新,咱們調用subscribe監聽到state發生變化後用getState獲取新的state數據。 redux的state和react的state二者徹底沒有關係,除了名字同樣。
若只使用redux,流程是這樣:
component --> dispatch(action) --> reducer --> subscribe --> getState --> component
使用react-redux以後,流程:
component --> actionCreator(data) --> reducer --> component
store的三大功能:dispatch,subscribe,getState都不須要手動來寫了。react-redux幫咱們作了這些,同時它提供了兩個好基友Provider和connect。 Provider是一個組件,它接受store做爲props,而後經過context往下傳,這樣react中任何組件均可以經過contex獲取store。也就意味着咱們能夠在任何一個組件裏利用dispatch(action)來觸發reducer改變state,並用subscribe監聽state的變化,而後用getState獲取變化後的值。可是並不推薦這樣作,它會讓數據流變的混亂,過分的耦合也會影響組件的複用,維護起來也更麻煩。 Connect --connect(mapStateToProps, mapDispatchToProps, mergeProps, options)是一個函數,它接受四個參數而且再返回一個函數--wrapWithConnect,wrapWithConnect接受一個組件做爲參數wrapWithConnect(component),它內部定義一個新組件Connect(容器組件)並將傳入的組件(ui組件)做爲Connect的子組件而後return出去。
mapStateToProps(state, [ownProps]):
mapStateToProps 接受兩個參數,store的state和自定義的props,並返回一個新的對象,這個對象會做爲props的一部分傳入ui組件。咱們能夠根據組件所須要的數據自定義返回一個對象。ownProps的變化也會觸發mapStateToProps
function mapStateToProps(state){
return {todos:state.todos};
}
複製代碼
mapDispatchToProps(dispatch,[ownProps]):
mapDispatchToProps若是是對象,那麼會和store綁定做爲props的一部分傳入ui組件。若是是個函數,它接受兩個參數,bindActionCreators會將action和dispatch綁定並返回一個對象,這個對象會和ownProps一塊兒做爲props的一部分傳入ui組件。因此不論mapDispatchToProps是對象仍是函數,它最終都會返回一個對象,若是是函數,這個對象的key值是能夠自定義的.
function mapDispatchToProps(dispatch) {
return {
todoActions: bindActionCreators(todoActionCreators, dispatch),
counterActions: bindActionCreators(counterActionCreators, dispatch)
};
}
複製代碼
mapDispatchToProps返回的對象其屬性其實就是一個個actionCreator,由於已經和dispatch綁定,因此當調用actionCreator時會當即發送action,而不用手動dispatch。ownProps的變化也會觸發mapDispatchToProps.
mergeProps(stateProps,dispatchProps,ownProps):
將mapStateToProps() 與 mapDispatchToProps()返回的對象和組件自身的props合併成新的props並傳入組件。默認返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的結果。
options:
pure = true 表示Connect容器組件將在shouldComponentUpdate中對store的state和ownProps進行淺對比,判斷是否發生變化,優化性能。爲false則不對比。
1、Provider組件接受redux的store做爲props,而後經過context往下傳。
2、connect函數在初始化的時候會將mapDispatchToProps對象綁定到store,若是mapDispatchToProps是函數則在Connect組件得到store後,根據傳入的store.dispatch和action經過bindActionCreators進行綁定,再將返回的對象綁定到store,connect函數會返回一個wrapWithConnect函數,同時wrapWithConnect會被調用且傳入一個ui組件,wrapWithConnect內部使用class Connect extends Component定義了一個Connect組件,傳入的ui組件就是Connect的子組件,而後Connect組件會經過context得到store,並經過store.getState得到完整的state對象,將state傳入mapStateToProps返回stateProps對象、mapDispatchToProps對象或mapDispatchToProps函數會返回一個dispatchProps對象,stateProps、dispatchProps以及Connect組件的props三者經過Object.assign(),或者mergeProps合併爲props傳入ui組件。而後在ComponentDidMount中調用store.subscribe,註冊了一個回調函數handleChange監聽state的變化。
3、此時ui組件就能夠在props中找到actionCreator,當咱們調用actionCreator時會自動調用dispatch,在dispatch中會調用getState獲取整個state,同時註冊一個listener監聽state的變化,store將得到的state和action傳給combineReducers,combineReducers會將state依據state的key值分別傳給子reducer,並將action傳給所有子reducer,reducer會被依次執行進行action.type的判斷,若是有則返回一個新的state,若是沒有則返回默認。combineReducers再次將子reducer返回的單個state進行合併成一個新的完整的state。此時state發生了變化。Connect組件中調用的subscribe會監聽到state發生了變化,而後調用handleChange函數,handleChange函數內部首先調用getState獲取新的state值並對新舊兩個state進行淺對比,若是相同直接return,若是不一樣則調用mapStateToProps獲取stateProps並將新舊兩個stateProps進行淺對比,若是相同,直接return結束,不進行後續操做。若是不相同則調用this.setState()觸發Connect組件的更新,傳入ui組件,觸發ui組件的更新,此時ui組件得到新的props,react --> redux --> react 的一次流程結束。
1、Provider組件接受redux的store做爲props,而後經過context往下傳。
2、connect函數收到Provider傳出的store,而後接受三個參數mapStateToProps,mapDispatchToProps和組件,並將state和actionCreator以props傳入組件,這時組件就能夠調用actionCreator函數來觸發reducer函數返回新的state,connect監聽到state變化調用setState更新組件並將新的state傳入組件。
connect能夠寫的很是簡潔,mapStateToProps,mapDispatchToProps只不過是傳入的回調函數,connect函數在必要的時候會調用它們,名字不是固定的,甚至能夠不寫名字。
connect(state => state,action)(Component);
複製代碼
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';
複製代碼
若是你以爲這篇文章對你有幫助的話~~
doreen's notes star一下啦~謝謝😀