在兩年前曾短暫的使用過react+redux進行開發,但是一入vue深似海,今後其餘是路人,趁着閒的時間,趕忙擼一把redux在react的實現。下面來簡單的和你們分享一下。html
在瞭解redux以前,有必要先了解一下flux, flux是Facebook用戶創建客戶端Web應用的前端架構,以單向數據流方式支持MVC, Flux應用有三個主要部分:Dispatcher調度,存儲Store 和 視圖View ,當一個用戶和視圖交互時,有一個分發器dispatcher用來發送動做action到數據存儲中,而後更新視圖,存儲接受更新,適當地調節這些更新,而不是一致地依賴外部更新其數據,存儲以外根本不知道它是如何管理領域數據的,這有助於實現一種清晰的分離關注。前端
這是flux的結構和數據流向圖,不難看出一個單向數據流是Flux模式的核心。vue
你們若是稍有了解或者曾經接觸的話,必定會聽過這樣一句話:若是說flux是一種思想,那麼redux就是其中一種實現。 固然還有其它的實現方式如MoBx等。react
Redux是對flux的一種演變,也是flux思想的一種子集。設計目的在於 Redux 試圖讓 state 的變化變得可預測,Redux使Javascript的狀態管理變得更加可預期,以一種新方式思考開發應用,這個方式是:狀態從一個初始狀態開始,被一系列動做序列改變,這種新方式是通往復雜Web應用的捷徑。ios
這張圖很詳細的解釋了redux的數據流向,對比flux能夠看出,redux依舊是經過action來描述將要觸發的行爲, 沒有dispatcher這個分發器了,當每次Action被觸發須要dispatch時,使用一個函數稱爲reducer來返回新的應用狀態。 redux保留的應用狀態是不可變的。有興趣能夠了解一下 immutable.js這個概念。redux的工做流程與其設計三大基本原則密不可分json
首先要明確一下在react中完整的redux流程須要那幾個對象 或者角色。redux
再來看它的工做原理axios
上述 2,3,4的合集咱們暫且將它稱爲store, 基本的角色有了,那麼還得作一些角色之間的關聯,好比組件,若是須要訪問store裏面的數據,那麼首先就要和store作連接,只有創建鏈接以後那麼他纔有一個訪問權。若是組件想要訪問store裏面的state,那麼而後他要定義一個動做類型,包含一個名稱和一些便於store識別的信息(可選),根據這個動做類型就是action, store會把他丟給reducer, reducer是store的大腦,能夠訪問store中的元數據,而後根據store丟過來的信息,reducer作出種種處理,最後返回一個結果。這個結果再由store傳遞給視圖方面,來使頁面更新。api
若是上面的這個闡述的不夠清楚的話,你們能夠想象一個模擬借書的場景 把各個角色代入進去,大體是這樣的,react-router
component/view ====》 借書人小王
state====》圖書館A
action====》是欲借的書的信息(三國演義)
reducer====> 圖書館管理員(易中天)
角色暫時這樣安放,那麼把場景帶入,
話說中國歷史博大精深,小王同窗從小最愛學歷史,並且對三國的歷史最感興趣,因而乎小王週五放學拿着借書卡就往國圖書館奔, (ps:借書人首先要在該圖書館辦一個借書卡,圖書館見卡才能放行) 走到門口,門衛一看有借書卡,來的仍是個小夥子,還誇了句:「小夥子年紀輕輕就這麼愛學習,好啊 真好啊。」 小王抿嘴一笑, 不置能否,徑直借書去了,但是走進去,小王蒙了,這麼多書,我找一本三國演義,我太難了,此時小王靈機一動突然想起,這裏頭的圖書館管理員,易中天,江湖人稱老易,掌管整個書館,絕對能找到,因而找到老易,老易是一個七八十歲老頭,一臉博學的文化人,看見小王也很高興,說小夥子,這回借什麼書,小王說:「易爺爺,此次想借一本三國演義」,老易說:「三國演義啊,行 等我想一想在哪」,約莫4,5秒,老易徑自拿出一本三國演義給了小王,小王大喜連聲道謝。因而捧書而走。 埋頭苦讀,遂成三國通。
整個場景與上面的闡述你們能夠思考一下,上面主要是講一下概念,你們最關心的確定是代碼實現 下面分步驟來解釋.
這是定義的上面那個列表的組件,那麼組件寫好以後,咱們就開始往裏頭填數據,數據從何而來,天然是redux。
reducer.js 內容以下:
import { fromJS } from 'immutable';
import * as constants from './constants';
const defaultState = fromJS({
writerList: [],
});
複製代碼
這個writerList就是咱們最後要渲染到WriterWrapper裏面的數據,接下來的一切行爲都是爲了取到這個writerList
WriterWrapper組件添加以下內容
import React, { PureComponent } from 'react';
import {connect} from 'react-redux'
const mapStateToProps = (state) =>{
return {
list: state.getIn(['home', 'writerList']) // 這一步取到爲空 動做還未觸發
}
}
const mapStateTodispatch = (dispatch) => {
return {
}
}
export default connect(mapStateToProps, mapStateTodispatch)(Writer);
複製代碼
react-redux拋出了一個connect方法 用來將react組件和store作綁定連接,connect方法接受兩個參數,一個是mapStateToProps,這個參數的做用是根據組件自身的狀態選擇自身須要的數據,由於這個方法它能夠訪問store裏面全部的元數據,一旦使用錯誤 會形成性能上的損耗,因此要謹慎使用,另外一個是mapStateTodispatch,包含一個dispatch方法,負責將視圖的行爲也就是action傳遞給reducer進行處理,最後跟上組件名稱則表示組件已經和redux進行了關聯,爲何用這種方式就能關聯,先跳過,咱們稍後再詳細說明。
到這裏須要明確兩件事: 1.我要拿到什麼。 2經過什麼方式拿 dispatch派發的action通常狀況下是一個對象,可是當涉及到異步時 action也能夠是一個函數,典型的例子就是上面這個writerList咱們須要從服務端取得,這個時候咱們可能須要定義一個函數類型的action代碼以下:
import { actionCreators } from '../store/index'
const mapStateToProps = (state) =>{
return {
list: state.getIn(['home', 'writerList'])
}
}
const mapStateTodispatch = (dispatch) => {
return {
getWriterList(){
dispatch(actionCreators.getWriterList())
}
}
}
componentDidMount(){
this.props.getWriterList()
}
複製代碼
list就是咱們最終要取得的數據,咱們須要經過getWriterList() 這個方法去獲取數據,由於已經作了關聯, 咱們能夠在生命週期鉤子裏去調用這個函數 此時dispatch觸發的是一個異步函數 actionCreators.js文件中的getWriterList()方法 爲了方便管理 能夠都統一拆分到一個actionCreators.js文件之中而後拋出 其代碼以下:
actionCreators.js內容以下
export const getWriterList = () => {
// 借用redux-chunk可使action處理異步 標記是返回的是不是函數
return (dispatch) => {
axios.get('/api/writerList.json').then(res=>{
const result = res.data.data;
dispatch(addWriterList(result))
}).catch(err=>{
console.log(err)
})
}
}
複製代碼
很明顯在這裏,咱們發出了一個請求, 將返回的結果再次dispatch觸發 此時將結果傳入到一個addWriterList()方法中,代碼以下:
export const ADD_WRITER_LIST = 'home/ADD_WRITER_LIST'; constants.js
actionCreators.js內容添加方法
import * as constants from './constants';
const addWriterList = (result) => {
return {
type: constants.ADD_WRITER_LIST,
writerList: fromJS(result.writerList)
}
}
複製代碼
這個方法最後return出一個對象 符合了非異步狀況下action通常是一個object的規則 是否是好奇這個constants,其實就是定義的actionType,reducer須要根據這個type來作出相應處理。
reducer是一個純函數,本質上傳入必定類型的數據,必然返回必定類型的結果,作具體的數據過濾尤爲合適,
reducer.js內容以下
const defaultState = fromJS({
writerList: [],
});
const addWriterList = (state, action)=>{
return state.set('writerList', action.writerList)
}
export const ADD_WRITER_LIST = 'home/ADD_WRITER_LIST'; constants.js
export default (state = defaultState, action) => {
switch(action.type) {
case constants.ADD_WRITER_LIST:
return addWriterList(state, action);
default:
return state;
}
}
複製代碼
reducer接受兩個參數 第一個是state元數據, 第二個是action視圖觸發的動做類型,經過這兩個信息 reducer就能夠返回符合指望的信息,參照全部圖書和三國演義的例子。根據對應的ADD_WRITER_LIST類型 執行完addWriterList方法後 此時 defaultState 的 writerList, 已是一個通過服務端數據填充的元數據,而由於組件與redux作了連接 因此頁面會正確的渲染出目標視圖,即簡書網站的列表組件。
你們可能一直有疑問爲何經過connect方法就能和組件作連接, connect背後的邏輯是這樣的
1.建立一個全局store對象
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(
applyMiddleware(thunk)
));
export default store;
複製代碼
相似上面的代碼你們確定都很熟悉,能夠先無論引入的reducer,只須要知道是處理具體的過濾邏輯便可,是一個龐大的模塊,從上面的redux模塊中 引入了createStore這個方法並執行, applyMiddleware是做爲中間件處理異步,thunk可使傳入的action不只僅是對象, 還能夠是函數,執行createStore()方法後把它拋出。 那咱們再來看reducer,reducer是一個龐大的模塊,那麼必然是有一個個小的模塊去組成的,每個小的模塊,又包含 redux的各類角色。這種拆分方式有利於各個模塊之間的數據管理不被污染
假如將咱們剛纔作的WriterWrapper組件看做一個小的store 那麼它的結構如今是這樣的
index.js內容以下
import reducer from './reducer';
import * as actionCreators from './actionCreators';
import * as constants from './constants';
export { reducer, actionCreators, constants };
這個文件引入了 剛纔所須要的全部內容 並作了拋出 咱們姑且把它放到home目錄下。
複製代碼
2 .建立一個全局reducer文件
import { combineReducers } from 'redux-immutable';
import { reducer as homeReducer } from '../pages/home/store';
const reducer = combineReducers({
home: homeReducer,
})
export default reducer;
複製代碼
這個文件引入了剛纔拋出的那個home目錄下的reducer對象,介紹一下combineReducers是爲了作多個reducer的合併
3.綁定視圖
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter, Route } from 'react-router-dom';
import Header from './common/header';
import Home from './pages/home';
import store from './store';
class App extends Component {
render() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Header />
<Route path='/' exact component={Home}></Route>
</div>
</BrowserRouter>
</Provider>
);
}
}
export default App;
複製代碼
經過react-redux的provider提供商 將整個容器包裹,因而就至關於整個store和應用作了連接,這時就有了組件的connect連接,這樣一個完整的redux流程就基本造成了。
一直糾結於文章的順序安排,有些地方順序安排的不合理,ps:(千萬別誤人子弟了)你們也能夠給出本身的看法,我會繼續補充修改本文。
文章參考: