摘要:
發覺在學習react的生態鏈中,react+react-router+webpack+es6+fetch等等這些都基本搞懂的差不多了,可以應用到實戰當中,唯獨這個redux還不能,學習redux還學的挺久的。
其中困擾我最久的就是redux的異步數據流的處理。難點主要是概念太多,接觸的詞太多,而且網上的案例看的頭都疼,很容易暈,已經暈了好多次了。后來被我簡化之后,終於搞懂了,哈哈。!來來來,今天總結一下,希望對大家有所幫助。不過本人主要是介紹redux的異步操作,如果對redux不是很熟悉的,可以看看我之前寫的一篇文章http://www.cnblogs.com/xianyulaodi/p/5399264.html
1、redux異步流程簡介
先來一張圖先,大概介紹一下redux的異步流程:
(圖片來自於百度圖片)
大概的流程是這樣的:
首先發起一個action,然后通過中間件,這里為什么要用中間件呢,因為這樣dispatch的返回值才能是一個函數。通過store.dispatch,將狀態的的改變傳給store的小弟,reducer,reducer根據action的改變,傳遞新的狀態state。最后將所有的改變告訴給它的大哥,store。store保存着所有的數據,並將數據注入到組件的頂部,這樣組件就可以獲得它需要的數據了。
2、demo簡介
這個demo是我根據官網的async改編的,其實也就是刪除了很多其他干擾的東西,讓它僅僅是獲得一個異步請求。初學嘛,能簡單則簡單,這樣比較明朗。
官網原版的github地址: https://github.com/lewis617/react-redux-tutorial/tree/master/redux-examples/async
我改編的github地址:https://github.com/xianyulaodi/reduxAsync
本文也是通過我自己改的來寫的。可以邊看代碼邊看此文,當然可能有些講的不是很對,歡迎指出
界面如下:
界面很簡單,就是通過異步請求,將請求到的數據展現到界面上。可能你會說,不就是一個請求嘛,有啥大不了的。可是這個請求是走了一遍redux的流程的呢,哈哈。
3、代碼解析
下面對redux的異步流程做一個小小的解析。期間順便也回顧一下redux的一些基礎知識吧。
3.1 首先從action開始,代碼在 action/index.js
1 import fetch from 'isomorphic-fetch' 2 export const RECEIVE_POSTS = 'RECEIVE_POSTS' 3 4 //獲取新聞成功的action 5 function receivePosts(reddit, json) { 6 return { 7 type: RECEIVE_POSTS, 8 reddit: reddit, 9 posts: json.data.children.map(child =>child.data) 10 } 11 } 12 13 function fetchPosts(subreddit) { 14 15 return function (dispatch) { 16 17 return fetch(`http://www.subreddit.com/r/${subreddit}.json`) 18 .then(response => response.json()) 19 .then(json => 20 dispatch(receivePosts(subreddit, json)) 21 ) 22 } 23 } 24 25 //如果需要則開始獲取文章 26 export function fetchPostsIfNeeded(subreddit) { 27 28 return (dispatch, getState) => { 29 30 return dispatch(fetchPosts(subreddit)) 31 32 } 33 }
此次的請求沒有用ajax,而是用了fetch,這個稍微了解一下即可,入門比較容易。如上面的redux異步流程圖一樣,它需要借助一個中間件 middleware。其實異步的精髓也就在這個中間件middleware這里了。搞懂了這個,可以說基本也就搞懂的redux的異步了。所以我會花比較大的篇幅去介紹這個中間件。
middleware有什么用?為什么要引入它呢?
在redux里,action僅僅是攜帶了數據的普通js對象( plain JavaScript objects)。action creator返回的值是這個action類型的對象。然后通過store.dispatch()進行分發……
action ---> dispatcher ---> reducers
如果遇到異步情況,比如點擊一個按鈕,希望2秒之后更新視圖,顯示消息“Hi”。我們可能這么寫ActionCreator:
var asyncSayActionCreator = function (message) { setTimeout(function () { return { type: 'SAY', message } }, 2000) }
這會報錯,因為這個asyncSayActionCreator返回的不是一個action,而是一個function。這個返回值無法被reducer識別。
也就是說,正常來說,action返回的是一個對象,而不是一個函數。如果返回函數,會出現錯誤。
而異步操作呢,需要action的返回值是一個函數。那么咋辦呢,所以需要引入中間件middleware,它在中間起到了橋梁的作用,讓action的返回值可以是一個函數,從而傳到
reducer那里。也就是說,中間件是用在action發起之后,reducer接收到之前的這個時間段。
也可以這么說,Middleware 主要是負責改變Store中的dispatch方法,從而能處理不同類型的 action 輸入,得到最終的 Javascript Plain Object 形式的 action 對象。
因此,上面那個ActionCreator就可以改寫為這樣:因為action的返回值是一個函數。
var asyncSayActionCreator = function (message) { return function (dispatch) { setTimeout(function () { dispatch({ type: 'SAY', message }) }, 2000) } }
中間件是如何工作的
Middleware的中間件有很多,不過我的這個案例只引用了其中的一個,那就是redux-thunk
redux-thunk源碼如下:
export default function thunkMiddleware({ dispatch, getState }) { return next => action => typeof action === 'function' ? action(dispatch, getState) : next(action); }
意思是如果action是一個函數,執行這個action函數,如果不是函數,執行next函數。
具體的原理可以看看這兩篇文章:
fetchPostsIfNeeded這里就是一個中間件。redux-thunk會攔截fetchPostsIfNeeded這個action,會先發起數據請求,如果成功,就將數據傳給action.從而到達reducer那里。
再來看看reducer 目錄為 reducers/index.js
1 import { combineReducers } from 'redux' 2 import { 3 RECEIVE_POSTS 4 } from '../actions' 5 6 7 function posts(state = { 8 items: [] 9 }, action) { 10 switch (action.type) { 11 12 case RECEIVE_POSTS: 13 // Object.assign是ES6的一個語法。合並對象,將對象合並為一個,前后相同的話,后者覆蓋強者。詳情可以看這里 14 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign 15 return Object.assign({}, state, { 16 items: action.posts //數據都存在了這里 17 }) 18 default: 19 return state 20 } 21 } 22 23 //廢棄、接收到、開始接受新聞后,將state.postsByReddit設為相關參數 24 function postsByReddit(state = { }, action) { 25 switch (action.type) { 26 case RECEIVE_POSTS: 27 return Object.assign({}, state, { 28 [action.reddit]: posts(state[action.reddit], action) 29 }) 30 default: 31 return state 32 } 33 } 34 35 // 將所有的reducer結合為一個,傳給store 36 const rootReducer = combineReducers({ 37 postsByReddit 38 }) 39 40 export default rootReducer
這個跟正常的reducer差不多。判斷action的類型,從而根據action的不同類型,返回不同的數據。這里將數據存儲在了items這里。這里的reducer只有一個。最后結合成rootReducer,傳給store。
store/configureStore.js
1 import { createStore, applyMiddleware } from 'redux' 2 import thunkMiddleware from 'redux-thunk' 3 import createLogger from 'redux-logger' 4 import rootReducer from '../reducers' 5 6 const createStoreWithMiddleware = applyMiddleware( 7 thunkMiddleware, 8 createLogger() 9 )(createStore) 10 11 export default function configureStore(initialState) { 12 const store = createStoreWithMiddleware(rootReducer, initialState) 13 14 if (module.hot) { 15 // Enable Webpack hot module replacement for reducers 16 module.hot.accept('../reducers', () => { 17 const nextRootReducer = require('../reducers') 18 store.replaceReducer(nextRootReducer) 19 }) 20 } 21 22 return store 23 }
我們是如何在 dispatch 機制中引入 Redux Thunk middleware 的呢?
我們使用了 applyMiddleware(),
const createStoreWithMiddleware = applyMiddleware(
thunkMiddleware,
createLogger()
)(createStore)
其中,createLogger是一個很便捷的 middleware,用來打印 action 日志。通過使用指定的 middleware,action creator 除了返回 action 對象外還可以返回函數。
這時,這個 action creator 就成為了 thunk。
再來看看界面上的調用:在containers/App.js
部分代碼:
//初始化渲染后觸發 componentDidMount() { const { dispatch} = this.props // 這里可以傳兩個值,一個是 reactjs 一個是 frontend dispatch(fetchPostsIfNeeded('frontend')) }
改變狀態的時候也是需要通過dispatch來傳遞的。
數據的獲取是通過provider,將store里面的數據注入給組件。讓頂級組件提供給他們的子孫組件調用。代碼如下:
import 'babel-core/polyfill' import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import App from './containers/App' import configureStore from './store/configureStore' const store = configureStore() render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
這樣就完成了redux的異步操作。其實最主要的區別還是action里面還有中間件的調用,其他的地方基本跟同步的redux差不多的。搞懂了中間件,就基本搞懂了redux的異步操
作。具體的大家還是需要看一下源碼和其他相應的文章介紹。當然,demo是不夠完善的,因為缺少請求發送失敗等等的處理。
再附上一張圖來結束今天的學習筆記:
今天的總結就到這里先,感覺自己總結的有點亂。剛好最近用react寫了一個小小的項目,下一步將異步和同步結合起來,真正用到實戰中,到時候再將這篇博客完善和補充。
由於是初學異步的操作,所以可能有些地方自己總結的錯了。有誤之處,歡迎指出。如果對於中間件還有很多困惑的同學,不妨看一下下面的鏈接。
參考:
http://www.cnblogs.com/bingooo/p/5500108.html#top
https://segmentfault.com/a/1190000003746223
https://zhuanlan.zhihu.com/p/20597452?utm_source=tuicool&utm_medium=referral