[React] 12 - Redux: async & middleware

Ref: Redux 入門教程(二):中間件與異步操做html

這裏只是簡單地瞭解中間件的概念,對於異步,貌似以後要講的saga更勝一籌。react

 

reducer計算新狀態的策略:ios

  • Action 發出之後,Reducer 當即算出 State,這叫作同步
  • Action 發出之後,過一段時間再執行 Reducer,這就是異步

 

 

何爲中間件


1、安插中間件的位置

middleware提供的是位於 action 被髮起以後,到達 reducer 以前的擴展點。編程

 

  • 原程序
const Counter = ({ value, onIncrement, onDecrement }) => (
  <div>
  <h1>{value}</h1>
  <button onClick={onIncrement}>+</button>
  <button onClick={onDecrement}>-</button>
  </div>
);

...............................................................

/**
* 更新過程: * action + old state ==> new state * 也能夠考慮combineReducers的形式 */
const reducer = (state = 0, action) => { switch (action.type) { case 'INCREMENT': return state + 1;                 // step 2: how to implement this action --> automatically trigger step 3: UI case 'DECREMENT': return state - 1; default: return state; } }; ...............................................................
const store = createStore(reducer);
/**
* 渲染過程:
* 既接收動做,也處理界面更新
* 固然,具體的更新html還要歸於具體的html代碼,也就是最上面的Counter組件的定義
*/ const render = () => { ReactDOM.render(
<Counter value={store.getState()}                       // step 3: render new ui based on new state onIncrement={() =>store.dispatch({type: 'INCREMENT'})}    // step 1: receive action --> automatically trigger step 2: reducer onDecrement={() => store.dispatch({type: 'DECREMENT'})} />, document.getElementById('root') ); }; render(); store.subscribe(render);

 

  • 功能加強

對store.dispatch作了新的定義:json

 --- 不只給store發送信號action。redux

 --- 並且附帶了log的功能。axios

let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  next(action);
  console.log('next state', store.getState());
}

 

再理解以下文字:c#

(1)Reducer:純函數,只承擔計算 State 的功能,不合適承擔其餘功能,也承擔不了,由於理論上,純函數不能進行讀寫操做。api

(2)View:與 State 一一對應,能夠看做 State 的視覺層,也不合適承擔其餘功能。數組

(3)Action:消息的載體,讓reducer的純函數去操做,本身不用幹活er。

 

 

2、中間件的用法

做爲createStore的參數來註冊。

applyMiddlewares(...),Redux 的原生方法,做用是將全部中間件組成一個數組,依次執行。

const store = createStore(
  reducer,
  initial_state,                   // 有第二參數則表示整個程序的初始狀態
  applyMiddleware(logger)
);

const store = createStore( reducer, applyMiddleware(thunk, promise, logger)  // 這裏的log必定要放在最後 );

 

 

3、實例分析

經過中間件,加強Log的用法。

  • 在reducer以前執行

store.dispatch ==> middleware --> logger ==> "action fired." 

 

  • 添加 next(action)

其實就是一個多層嵌套返回函數的函數,

柯里化 - 使用箭頭的寫法在函數式編程,對柯里化更詳細的介紹能夠看一看這篇 張鑫旭的博客

第一個(最外層)方法的參數是一個包含dispatch和getState字段(方法)的對象,其實就是store對象,因此也能夠寫成:

# 一個Redux中間件的基本寫法

store => next => action => { ... };

參數next是一個方法,做用是:通知下一個Redux中間件對此次的action進行處理。

next(action),若是一箇中間件中沒有執行next(action),則action會中止向後續的中間件傳遞,並阻止reducer的執行(store將不會由於本次的action而更新)。

 

import { applyMiddleware, createStore } from "redux";

const reducer = (initialState=0, action) => {
  if (action.type === "INC") {
    return initialState + 1;
  } else if (action.type === "DEC") {
    return initialState - 1;
  } else if (action.type === "MULT") {
    throw new Error("AHHHH!!");
  }
  return initialState;
}
-------------------------------------------------------------
const logger
= (store) => (next) => (action) => { console.log("Logged", action); return next(action); }; const errorHandler = (store) => (next) => (action) => { try { return next(action); } catch(e) { console.log("ERROR!", e); } }; const middleware= applyMiddleware( logger, errorHandler )

-------------------------------------------------------------
const store
= createStore(reducer, middleware) store.subscribe(() => { console.log("store changed", store.getState()); }) store.dispatch({type: "INC"}) store.dispatch({type: "INC"}) store.dispatch({type: "INC"}) store.dispatch({type: "DEC"}) store.dispatch({type: "DEC"}) store.dispatch({type: "DEC"}) store.dispatch({type: "MULT"}) store.dispatch({type: "DEC"})

 

  • redux-logger 使用

 

 

  

異步操做實現


1、用戶送出第一個 Action

Ref: RUAN的博文可能更容易理解些

若是發送的信號(action)涉及到服務端,那麼異步就是不可避免的事情。

  • 整個異步操做的思路:
第一個action:操做開始時,送出一個 Action,觸發 State 更新爲"正在操做"狀態,View 從新渲染
第二個action:操做結束後,再送出一個 Action,觸發 State 更新爲"操做結束"狀態,View 再一次從新渲
  • 異步須要三種action,三種 Action 能夠有兩種不一樣的寫法:
// 寫法一:名稱相同,參數不一樣
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }

// 寫法二:名稱不一樣
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }
  • State 也要進行改造,反映不一樣的操做狀態:
let state = {
  // ... 
  isFetching: true,      // 表示是否在抓取數據
  didInvalidate: true,    // 表示數據是否過期
  lastUpdated: 'xxxxxxx'   // 表示上一次更新時間
};

 

 

2、自動送出第二個 Action

  • 代碼背景

異步操做至少要送出兩個 Action:

    1. 用戶觸發第一個 Action,這個跟同步操做同樣,沒有問題;
    2. 如何才能在操做結束時,系統自動送出第二個 Action 呢?
# 異步組件的例子

class AsyncApp extends Component {
componentDidMount() { const { dispatch, selectedPost } = this.props
dispatch(fetchPosts(selectedPost))  // fetchPosts是送給server的信號(action)
} // ...
 

 

  • Step 1 - 返回的是一個函數,而非」對象」

What kind of ActionCreator it is?  

 (1) 先發出一個ActionrequestPosts(postTitle)) ----> 而後進行異步操做。

 (2) 拿到結果後,先將結果轉成 JSON 格式 ----> 而後再發出一個 Action( receivePosts(postTitle, json)

const fetchPosts = postTitle => (dispatch, getState) => {
  dispatch(requestPosts(postTitle));
  return fetch(`/some/API/${postTitle}.json`) .then(response => response.json()) .then(json => dispatch(receivePosts(postTitle, json)));
  };
};

 

  • Step 2 - 如何使用
// 使用方法一
store.dispatch( fetchPosts('reactjs') );
// 使用方法二 store.dispatch( fetchPosts('reactjs') ).then(() => console.log(store.getState()) );

返回的函數的參數:dispatchgetState這兩個 Redux 方法,而非 Action 的內容。

 

  • 注意事項

(1)fetchPosts 返回了一個函數,而普通的 Action Creator 默認返回一個對象。

(2)返回的函數的參數是dispatchgetState這兩個 Redux 方法,普通的 Action Creator 的參數是 Action 的內容。

(3)在返回的函數之中,先發出一個 Action(requestPosts(postTitle)),表示操做開始。

(4)異步操做結束以後,再發出一個 Action(receivePosts(postTitle, json)),表示操做結束。

 

 

3、中間件幫助"參數擴展"

  • redux-thunk:方案一,使參數支持 「函數"

經過中間件redux-thunk,改造store.dispatch,使得後者能夠接受函數做爲參數。

import { createStore, applyMiddleware } from 'redux';
import thunk   from 'redux-thunk';
import reducer from './reducers';

// Note: this API requires redux@>=3.1.0
const store = createStore(    // 其實就是返回了一個特殊的store,是store.dispatch支持函數做爲參數了
  reducer,
  applyMiddleware(thunk)
);

 

  • redux-promise:方案二,使參數支持 「Promise對象」

讓 Action Creator 返回一個 Promise 對象,乃另外一種異步操做的解決方案 through 使用redux-promise中間件。

import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware  from 'redux-promise';
import reducer            from './reducers';

const store = createStore(
  reducer,
  applyMiddleware(promiseMiddleware)
); 

 

 

詳情請見[JS] ECMAScript 6 - Async : compare with c#

所謂Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。

 

 

接受 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 對象。

import { createAction } from 'redux-actions';

class AsyncApp extends Component {
componentDidMount() { const { dispatch, selectedPost } = this.props
// 發出同步 Action dispatch(requestPosts(selectedPost));
// 發出異步 Action, 只有等到操做結束,這個 Action 纔會實際發出;
// 注意,的第二個參數必須是一個 Promise 對象。
dispatch(createAction( 'FETCH_POSTS',            // 第一個參數 fetch(`/some/API/${postTitle}.json`)    // 第二個參數 .then(response => response.json()) )); }
createAction

 

代碼舉例子:payload屬性是一個 Promise 對象

 ----> Without promise, 發送完信號後,還要聽過axios.then...catch...定義」返回狀態處理「函數。

 ----> With promise, 以下使用axios (基於promise的http庫)。

import { applyMiddleware, createStore } from "redux";
import axios   from "axios";
import logger  from "redux-logger";
import thunk   from "redux-thunk";
import promise from "redux-promise-middleware";

const initialState = {
  fetching: false,
  fetched:  false,
  users:    [],
  error:    null,
};

// 改變state的部分value const reducer
= (state=initialState, action) => { switch (action.type) {
/**
* 由於使用了promise,因此默認使用promise的性質
* 其中包括了promise的三個狀態定義:pending, rejected, fulfilled
*/
case "FETCH_USERS_PENDING": { return {...state, fetching: true} break; } case "FETCH_USERS_REJECTED": { return {...state, fetching: false, error: action.payload} break; } case "FETCH_USERS_FULFILLED": { return { ...state, fetching: false, fetched: true, users: action.payload, } break; } } return state } const middleware = applyMiddleware(promise(), thunk, logger()) const store = createStore(reducer, middleware)
// 由於promise,這裏就省去了 store.dispatch({ type:
"FETCH_USERS",  # 自定發送」添加promose默認後綴「後的信號(action) payload: axios.get("http://rest.learncode.academy/api/wstern/users") })
相關文章
相關標籤/搜索