react+redux+router異步數據獲取教程

react的FLUX數據流一直搞不清楚,他不像Angular的雙向數據綁定,作一個model獲取數據,而後經過controller來管理view上的數據顯示就能夠了。單項數據流引入了太多的概念,stateactionreducerdispatch。就算看的懂圖,也不必定能coding出來。javascript

不過我總算先搞定了Redux
redux img前端

keywords

  • storejava

  • reducerreact

  • action編程

  • dispatchjson

  • connectredux

  • routerapi

  • middleware數組

  • thunkpromise

Basic Usage

1st 實現action方法

export const addDeck = name => ({ type: 'ADD_DECK', data: name });

2nd 根據action方法建立reducer方法

export const showBack = (state, action) => {
  switch(action.type) {
    case 'SHOW_BACK':
      return action.data || false;
    default:
      return state || false;
  }
};

3rd 根據reducer方法建立store

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();

4th 引入react-redux的<Provider>,導入store

<Provider store={store}>
    {...}
</Provider>

5th react組件中經過connect方法綁定store和dispatch。

const mapStateToProps = (newTalks) => ({
    newTalks
});

const mapDispatchToProps = dispatch => ({
    testFunc: () => dispatch(updataTalkLists(1)),
    receiveData: () => dispatch(receiveData())
});

export default connect(mapStateToProps, mapDispatchToProps)(MainPage);

6th this.props中直接調用action方法。

this.props.receiveData

With react-router

結合router使用時須要有2步。

1st 綁定routing到reducer上

import { syncHistoryWithStore, routerReducer } from 'react-router-redux';
import * as reducers from './redux/reducer';
reducers.routing = routerReducer;

const store = createStore(combineReducers(reducers));

2nd 使用syncHistoryWithStore綁定store和browserHistory

const history = syncHistoryWithStore(browserHistory, store);

        <Provider store={store}>
           <Router history={history}>
               {routes}
           </Router>
        </Provider>

Async

相似 Express 或 Koa 框架中的中間件。它提供的是位於 action 被髮起以後,到達 reducer 以前的擴展。
中間件的設計使用了很是多的函數式編程的思想,包括:高階函數,複合函數,柯里化和ES6語法,源碼僅僅20行左右。
項目中主要使用了三個中間件,分別解決不一樣的問題。

  • thunkMiddleware:處理異步Action

  • apiMiddleware:統一處理API請求。通常狀況下,每一個 API 請求都至少須要 dispatch 三個不一樣的 action(請求前、請求成功、請求失敗),經過這個中間件能夠很方便處理。

  • loggerMiddleware:開發環境調試使用,控制檯輸出應用state日誌

實現action異步操做,必需要引入middleware。我這裏用了applyMiddleware(thunkMiddleware)組件,也能夠用其餘的。

1st 建立store是引入Middleware

import thunkMiddleware from 'redux-thunk';
import { createStore, combineReducers, applyMiddleware } from 'redux';

const store = createStore(combineReducers(reducers), applyMiddleware(thunkMiddleware));

2nd 建立一個能夠執行dispacth的action

這也是中間件的做用所在。

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)));
  };
};

3rd 組件中對異步的store元素有相應的判斷操做。

React組件會在store值發生變化時自動調用render()方法,更新異步數據。可是咱們一樣也須要處理異步數據沒有返回或者請求失敗的狀況。不然渲染會失敗,頁面卡住。

if(!data.newTalks) {
   return(<div/>);
}

相關知識

Store的實現

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 };
};

combineReducer的簡單實現

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在中間件間的傳遞。

redux-promise中間件

既然 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()

  • 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()

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-thunk

咱們知道,異步調用何時返回前端是沒法控制的。對於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放到中間件列表的首位,防止其餘中間件中返回異步請求。

相關文章
相關標籤/搜索