react的FLUX數據流一直搞不清楚,他不像Angular
的雙向數據綁定,作一個model
獲取數據,而後經過controller
來管理view上
的數據顯示就能夠了。單項數據流引入了太多的概念,state
、action
、reducer
、dispatch
。就算看的懂圖,也不必定能coding出來。javascript
不過我總算先搞定了Redux
。
前端
storejava
reducerreact
action編程
dispatchjson
connectredux
routerapi
middleware數組
thunkpromise
export const addDeck = name => ({ type: 'ADD_DECK', data: name });
export const showBack = (state, action) => { switch(action.type) { case 'SHOW_BACK': return action.data || false; default: return state || false; } };
const store = createStore(combineReducers(reducers));
store.subscribe()
方法設置監聽函數,一旦 State 發生變化,就自動執行這個函數。
store.subscribe(listener);
顯然,只要把 View 的更新函數(對於 React 項目,就是組件的render方法或setState方法)放入listen,就會實現 View 的自動渲染。
store.subscribe方法返回一個函數,調用這個函數就能夠解除監聽。
let unsubscribe = store.subscribe(() => console.log(store.getState()) ); unsubscribe();
<Provider store={store}> {...} </Provider>
const mapStateToProps = (newTalks) => ({ newTalks }); const mapDispatchToProps = dispatch => ({ testFunc: () => dispatch(updataTalkLists(1)), receiveData: () => dispatch(receiveData()) }); export default connect(mapStateToProps, mapDispatchToProps)(MainPage);
this.props.receiveData
結合router使用時須要有2步。
import { syncHistoryWithStore, routerReducer } from 'react-router-redux'; import * as reducers from './redux/reducer'; reducers.routing = routerReducer; const store = createStore(combineReducers(reducers));
const history = syncHistoryWithStore(browserHistory, store); <Provider store={store}> <Router history={history}> {routes} </Router> </Provider>
相似 Express 或 Koa 框架中的中間件。它提供的是位於 action 被髮起以後,到達 reducer 以前的擴展。
中間件的設計使用了很是多的函數式編程的思想,包括:高階函數,複合函數,柯里化和ES6語法,源碼僅僅20行左右。
項目中主要使用了三個中間件,分別解決不一樣的問題。
thunkMiddleware:處理異步Action
apiMiddleware:統一處理API請求。通常狀況下,每一個 API 請求都至少須要 dispatch 三個不一樣的 action(請求前、請求成功、請求失敗),經過這個中間件能夠很方便處理。
loggerMiddleware:開發環境調試使用,控制檯輸出應用state日誌
實現action異步操做,必需要引入middleware。我這裏用了applyMiddleware(thunkMiddleware)
組件,也能夠用其餘的。
import thunkMiddleware from 'redux-thunk'; import { createStore, combineReducers, applyMiddleware } from 'redux'; const store = createStore(combineReducers(reducers), applyMiddleware(thunkMiddleware));
這也是中間件的做用所在。
export const receiveData = data => ({ type: 'RECEIVE_DATA', data: data }); export const fetchData = () => { return dispatch => { fetch('/api/data') .then(res => res.json()) .then(json => dispatch(receiveData(json))); }; };
React組件會在store值發生變化時自動調用render()方法,更新異步數據。可是咱們一樣也須要處理異步數據沒有返回或者請求失敗的狀況。不然渲染會失敗,頁面卡住。
if(!data.newTalks) { return(<div/>); }
Store提供了3個方法
import { createStore } from 'redux'; let { subscribe, //監聽store變化 dispatch, //調用action方法 getState //返回當前store } = createStore(reducer);
下面是create方法的一個簡單實現
const createStore = (reducer) => { let state; let listeners = []; const getState = () => state; const dispatch = (action) => { state = reducer(state, action); listeners.forEach(listener => listener()); }; const subscribe = (listener) => { listeners.push(listener); return () => { listeners = listeners.filter(l => l !== listener); } }; dispatch({}); return { getState, dispatch, subscribe }; };
const combineReducers = reducers => { return (state = {}, action) => { return Object.keys(reducers).reduce( (nextState, key) => { nextState[key] = reducers[key](state[key], action); return nextState; }, {} ); }; };
createStore方法能夠接受整個應用的初始狀態做爲參數,那樣的話,applyMiddleware就是第三個參數了。
中間件的次序有講究,logger就必定要放在最後,不然輸出結果會不正確。
const store = createStore( reducer, initial_state, applyMiddleware(thunk, promise, logger) );
applyMiddlewares的實現,它是將全部中間件組成一個數組,依次執行
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer); var dispatch = store.dispatch; var chain = []; var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }; chain = middlewares.map(middleware => middleware(middlewareAPI)); dispatch = compose(...chain)(store.dispatch); return {...store, dispatch} } }
上面代碼中,全部中間件被處理後獲得一個數組保存在chain中。以後將chain傳給compose,並將store.dispatch傳給返回的函數。。能夠看到,中間件內部(middlewareAPI)能夠拿到getState和dispatch這兩個方法。
那麼在這裏面作了什麼呢?咱們再看compose的實現:
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } else { const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) } }
compose中的核心動做就是將傳進來的全部函數倒序(reduceRight)進行以下處理:
(composed, f) => f(composed)
咱們知道Array.prototype.reduceRight是從右向左累計計算的,會將上一次的計算結果做爲本次計算的輸入。再看看applyMiddleware中的調用代碼:
dispatch = compose(...chain)(store.dispatch)
compose函數最終返回的函數被做爲了dispatch函數,結合官方文檔和代碼,不可貴出,中間件的定義形式爲:
function middleware({dispatch, getState}) { return function (next) { return function (action) { return next(action); } } } 或 middleware = (dispatch, getState) => next => action => { next(action); }
也就是說,redux的中間件是一個函數,該函數接收dispatch和getState做爲參數,返回一個以dispatch爲參數的函數,這個函數的返回值是接收action爲參數的函數(能夠看作另外一個dispatch函數)。在中間件鏈中,以dispatch爲參數的函數的返回值將做爲下一個中間件(準確的說應該是返回值)的參數,下一個中間件將它的返回值接着往下一個中間件傳遞,最終實現了store.dispatch在中間件間的傳遞。
既然 Action Creator 能夠返回函數,固然也能夠返回其餘值。另外一種異步操做的解決方案,就是讓 Action Creator 返回一個 Promise 對象。
寫法一,返回值是一個 Promise 對象。
const fetchPosts = (dispatch, postTitle) => new Promise(function (resolve, reject) { dispatch(requestPosts(postTitle)); return fetch(`/some/API/${postTitle}.json`) .then(response => { type: 'FETCH_POSTS', payload: response.json() }); });
寫法二,Action 對象的payload屬性是一個 Promise 對象。這須要從redux-actions模塊引入createAction方法,而且寫法也要變成下面這樣。
import { createAction } from 'redux-actions'; class AsyncApp extends Component { componentDidMount() { const { dispatch, selectedPost } = this.props // 發出同步 Action dispatch(requestPosts(selectedPost)); // 發出異步 Action dispatch(createAction( 'FETCH_POSTS', fetch(`/some/API/${postTitle}.json`) .then(response => response.json()) )); }
上面代碼中,第二個dispatch方法發出的是異步 Action,只有等到操做結束,這個 Action 纔會實際發出。注意,createAction的第二個參數必須是一個 Promise 對象。
redux-promise的源碼
export default function promiseMiddleware({ dispatch }) { return next => action => { if (!isFSA(action)) { return isPromise(action) ? action.then(dispatch) : next(action); } return isPromise(action.payload) ? action.payload.then( result => dispatch({ ...action, payload: result }), error => { dispatch({ ...action, payload: error, error: true }); return Promise.reject(error); } ) : next(action); }; }
從上面代碼能夠看出,若是 Action 自己是一個 Promise,它 resolve 之後的值應該是一個 Action 對象,會被dispatch方法送出(action.then(dispatch)),但 reject 之後不會有任何動做;若是 Action 對象的payload屬性是一個 Promise 對象,那麼不管 resolve 和 reject,dispatch方法都會發出 Action。
mapStateToProps是一個函數。它的做用就是像它的名字那樣,創建一個從(外部的)state對象到(UI 組件的)props對象的映射關係
mapStateToProps會訂閱 Store,每當state更新的時候,就會自動執行,從新計算 UI 組件的參數,從而觸發 UI 組件的從新渲染。
mapStateToProps的第一個參數老是state對象,還可使用第二個參數,表明容器組件的props對象。
使用ownProps做爲參數後,若是容器組件的參數發生變化,也會引起 UI 組件從新渲染。
connect方法能夠省略mapStateToProps參數,那樣的話,UI 組件就不會訂閱Store,就是說 Store 的更新不會引發 UI 組件的更新。
// 容器組件的代碼 // <FilterLink filter="SHOW_ALL"> // All // </FilterLink> const mapStateToProps = (state, ownProps) => { return { active: ownProps.filter === state.visibilityFilter } }
mapDispatchToProps是connect函數的第二個參數,用來創建 UI 組件的參數到store.dispatch方法的映射。也就是說,它定義了哪些用戶的操做應該看成 Action,傳給 Store。它能夠是一個函數,也能夠是一個對象。
mapDispatchToProps
做爲函數,應該返回一個對象,該對象的每一個鍵值對都是一個映射,定義了 UI 組件的參數怎樣發出 Action。
const mapDispatchToProps = ( dispatch, ownProps ) => { return { onClick: () => { dispatch({ type: 'SET_VISIBILITY_FILTER', filter: ownProps.filter }); } }; }
若是mapDispatchToProps
是一個對象,它的每一個鍵名也是對應 UI 組件的同名參數,鍵值應該是一個函數,會被看成 Action creator
,返回的 Action
會由 Redux
自動發出。
const mapDispatchToProps = { onClick: (filter) => { type: 'SET_VISIBILITY_FILTER', filter: filter }; }
<Provider>
組件React-Redux 提供Provider組件,可讓容器組件拿到state,它的原理是React組件的context屬性,請看源碼。
class Provider extends Component { getChildContext() { return { store: this.props.store }; } render() { return this.props.children; } } Provider.childContextTypes = { store: React.PropTypes.object }
上面代碼中,store放在了上下文對象context上面。而後,子組件就能夠從context拿到store,代碼大體以下。
class VisibleTodoList extends Component { componentDidMount() { const { store } = this.context; this.unsubscribe = store.subscribe(() => this.forceUpdate() ); } render() { const props = this.props; const { store } = this.context; const state = store.getState(); // ... } } VisibleTodoList.contextTypes = { store: React.PropTypes.object }
咱們知道,異步調用何時返回前端是沒法控制的。對於redux這條嚴密的數據流來講,如何才能作到異步呢。redux-thunk的基本思想就是經過函數來封裝異步請求,也就是說在actionCreater中返回一個函數,在這個函數中進行異步調用。咱們已經知道,redux中間件只關注dispatch函數的傳遞,並且redux也不關心dispatch函數的返回值,因此只須要讓redux認識這個函數就能夠了。
看了一下redux-thunk的源碼:
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
這段代碼跟上面咱們看到的中間件沒有太大的差異,惟一一點就是對action作了一下以下判斷:
if (typeof action === 'function') { return action(dispatch, getState, extraArgument); }
也就是說,若是發現actionCreater傳過來的action是一個函數的話,會執行一下這個函數,並以這個函數的返回值做爲返回值。前面已經說過,redux對dispatch函數的返回值不是很關心,所以此處也就無所謂了。
這樣的話,在咱們的actionCreater中,咱們就能夠作任何的異步調用了,而且返回任何值也無所謂,因此咱們可使用promise了:
function actionCreate() { return function (dispatch, getState) { // 返回的函數體內自由實現。。。 Ajax.fetch({xxx}).then(function (json) { dispatch(json); }) } }
最後還須要注意一點,因爲中間件只關心dispatch的傳遞,並不限制你作其餘的事情,所以咱們最好將redux-thunk放到中間件列表的首位,防止其餘中間件中返回異步請求。