星期二, 7月 12, 2016

Redux middleware 筆記

使用redux middleware完成非同步呼叫

我們團隊在開始使用action->dispatch->reducer其實進入蠻快的
但是當進入到非同步的時候就卡關了
因為要在action creator要處理非同步,如果不使用middleware
我們可能會在action creator

export function addTodo(text) {
  setTimeout(function () { 
return { type: ADD_TODO, text }
  },3000)
}
但是reducer會跟你說錯,因為回來的東西並不是action而是一個function,reducer無法處理,這時候就需要middleware處理了。


我們大概可以把 middleware當作redux流程中的一部分。

action->dispatcher->customized async middleware-> reducer 產生新的state。

============async call sample===========

var Promise = require('bluebird');
var Superagent = require('superagent');

function asyncMiddleware(){
 return function store(store){ 
   return function next(next){ //next middleware
    return function actionHandle(action){ 
    if (typeof action.ASYNC_CALL !== 'undefined') {
     return next(action); 
     //如果action沒有關鍵字ASYNC_CALL,則直接走下一個middleware
    }
    var type = action.type;
    
    return new Promise(function promiseHandle(resolve, reject){
     //這邊我們使用bluebird的Promise來達成promise
     
     function responseCallback(responseError, response){
      //some code error handle or resolve promise

      next({
       type: type,
       response: response
      });
      
      resolve(response);
      
     }
     
     //do ajax call
     //這邊我們是使用superagent 來get/put/post/delete
     Superagent.get(target).query(params).set(header).end(responseCallback);
    });
   }
  }
 }
}

=========action creator=================
而我們的action creator就很簡單了
例如下面這個是非同步呼叫載入個人資料



export const LOADED_PROFILE = 'LOADED_PROFILE';
export function loadProfile(params) {
 return {
  'ASYNC_CALL': { //有此關鍵字 middleware會走 ajax call
   type: LOADED_PROFILE,
   method: 'get',
   target: '/profile/:pid',
   params: params
  }
 };
};

=========component/container===========

import { loadProfile } from 'actions/profile';

class Profile  extends Component {
 componentDidMount() {
  var params = {};//get/post的參數 
  this.props.loadProfile(params).then(function(result) {
   //do something
  });
 }
}
var mapDispatchToProps = { loadProfile };
export default connect(mapStateToProps, mapDispatchToProps)(Profile);


=========小結=====================

透過這種方式,團隊成員在使用redux流程都會很一致的
action->(middlewares)->reducer->store

不用知道middlewares的細節就可以完成開發
(不過當然我都有解釋給他們聽原理)



=========附註======================

我們是從redux-thunk的source code理解實作方法的
https://github.com/gaearon/redux-thunk/blob/master/src/index.js
只有短短10多行程式碼

redux middleware的說明官方文件跟有人從頭跑過一次redux middleware的中文說明
http://redux.js.org/docs/advanced/Middleware.html
http://huli.logdown.com/posts/294284-javascript-redux-middleware-details-tutorial



星期日, 7月 10, 2016

React-Redux 筆記

react-redux其實概念我整理如下

  • 有個中央控管的rootStore
  • 元件把自己需要的rootStore map回去變成自己的props
  • 元件可以發出action去變動rootStore 進而更新自己的畫面


=================Store層========================
rootStore管理整個React App的資料(我們先稱他為root)
ex:

{ root : 
 profile:{...profileData},
 friend: {...friendData},
 privacy: {...privacy}
}
=================Action定義層========================
action會定義我們的操作
ex:

const ADD_PROFILE_DATA = 'ADD_PROFILE_DATA';
addProfileData(data) {
 {
 type: ADD_PROFILE_DATA,// for reducer使用
 data:data //這個action會變動的資訊
 }
}

=================Reducer定義層========================
reducer負責處理 action來的狀態變化
所以跟rootStore類似,我們會拆幾個reducer來處理


rootReducer= combineReducers({
 profileReducer,
 friendReducer,
 privacyReducer
});

const profileReducer = function (state, action ) {

  switch(action.type) //action所定義的type
    case 'ADD_PROFILE_DATA'

    state.profile = action.data
    return state; //這兩個的意義 (state, action) => new state
  
    default: 
    return state;//沒定義的action不變動state
}

=================Component/Container(view)定義層=========
React-redux的元件跟react元件最大的不同就是有引入react-redux的connect()方法。



import { connect } from 'react-redux';
import {addProfileData} from 'action/profile';
class Profile extends Component { 
//react lifecycle method, render methor
}
mapStateToProps (state) {
  profile: state.profile 
  //我的Profile只需要profile的資料
  //就map rootStore的 profile變成自己的props
  //如果今天有其他的action變動到rootStore profile的資料
  //Profile也會跟著更新狀態
}

export default connect( 
  mapStateToProps, { addProfileData } ,{/*mergeProps*/},{/*option*/})( Profile );


要熟悉react-redux 對他提供的connect一定要熟悉
第一個連接的是這個component的mapStateToPros 這個function
第二個是這個component會發出的action,官方是稱他為 mapDispatchToProps
做了上述兩個動作,這個react component就在redux資料流當中了

this.props.profile就有相對應的資料
this.props.addProfileData 也可以dispatch透過reducer,更新rootStore的資料。

第三個參數 mergeProps 第四個為option
後兩個比較少用,避免混淆觀念,我這篇先跳過,有興趣可以參閱官方API
https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-apdispatchtoprops-mergeprops-options


=================後序========================
以上是最基本的redux在react的應用流程,
之後還有要跟大家說的,如果redux會使用到非同步呼叫或是其他功能需求(ex logger),
我們是使用redux middleware來完成。