打入react&redux(2)- 神秘的dispatch
23 July 2016

action中dispatch的使用疑惑

当在项目中createStoreapplyMiddlewareredux-thunkthunkMiddleware后,action里的dispatch就变得很魔性,加也可以,不加也可以,譬如以下2种写法都能达到预期的效果。

export function fetchPosts(reddit) {
  return {
    type: REQUEST_POSTS,
    reddit
  }
}

或者

export function fetchPosts(reddit) {
  return dispatch => {
    return dispatch({     
      type: REQUEST_POSTS,
      reddit
    })
  }
}

那么在派发action的函数里用dispatch和不使用dispatch有什么区别

从函数调用栈挖出dispatch干了啥

情形1:return dispatch(action)

先看加了dispatch的函数调用栈

export function fetchPosts(reddit) {
  return dispatch => {
    return dispatch({     
      type: REQUEST_POSTS,
      reddit
    })
  }
}

image

按图中标注我们来看看每个核心步骤都在干什么。

  1. view层调用dispatch分发action

     componentDidMount() {
         const { dispatch, selectedReddit } = this.props
         dispatch(fetchPostsIfNeeded(selectedReddit))     //执行点
     }
    
  2. 进入thunkMiddleware

     function thunkMiddleware(_ref) {
       var dispatch = _ref.dispatch;
       var getState = _ref.getState;
    	
       return function (next) {
         return function (action) {
           return typeof action === 'function' ? action(dispatch, getState) : next(action); //  执行点,action为
         };
     }
    

    这个函数在全过程中执行了2次,这一次执行时,action的值为:

       dispatch => {
         return dispatch({
             type: REQUEST_POSTS,
             reddit
           })
        }
       };
    

    可见action为一个函数, thunkMiddleware将dispatch和getState方法注入到这个函数中并执行该函数。

  3. return dispatch(action)。

     export function fetchPostsIfNeeded(reddit) {
       return dispatch => {
         return dispatch({     //执行点
           type: REQUEST_POSTS,
           reddit
         })
       }
     }
    
  4. 进入applyMiddleware中执行封装的供中间件使用的dispatch方法。

     function applyMiddleware() {
       for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
         middlewares[_key] = arguments[_key];
       }
    	
       return function (createStore) {
         return function (reducer, initialState, enhancer) {
           var store = createStore(reducer, initialState, enhancer);
           var _dispatch = store.dispatch;
           var chain = [];
    	
           var middlewareAPI = {
             getState: store.getState,
             dispatch: function dispatch(action) {
               return _dispatch(action);     //执行点
             }
           };
           chain = middlewares.map(function (middleware) {
             return middleware(middlewareAPI);
           });
           _dispatch = _compose2["default"].apply(undefined, chain)(store.dispatch);
    	
           return _extends({}, store, {
             dispatch: _dispatch
           });
         };
       };
     }
    
  5. 同2中一致,再次进入thunkMiddleware,这次action为action对象,将直接调用next(action):

    { type: REQUEST_POSTS, reddit }

  6. 这是因为加了redux-logger,调用redux-logger中间件处理

  7. 真正执行store中的dispatch函数,并去调用currentReducer获取已最新的state状态。以下是redux中createStore.js里dispatch方法的源码。

     function dispatch(action) {
         if (!(0, _isPlainObject2["default"])(action)) {
           throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
         }
    	
         if (typeof action.type === 'undefined') {
           throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
         }
    	
         if (isDispatching) {
           throw new Error('Reducers may not dispatch actions.');
         }
    	
         try {
           isDispatching = true;
           currentState = currentReducer(currentState, action);     //执行点
         } finally {
           isDispatching = false;
         }
    	
         var listeners = currentListeners = nextListeners;
         for (var i = 0; i < listeners.length; i++) {
           listeners[i]();
         }
    	
         return action;
     }
    
  8. combineReducer将action分发到相应的reducer的state分支进行更新。

  9. reducer中更新state

     function postsByReddit(state = { }, action) {
       switch (action.type) {
         case INVALIDATE_REDDIT:
         case RECEIVE_POSTS:
         case REQUEST_POSTS:
           return Object.assign({}, state, {
             [action.reddit]: posts(state[action.reddit], action)
           })
         default:
           return state
       }
     }
    

情形二:直接return action

image

这种使用方式会从1直接执行到5,没有thunkMiddleware中调用action(dispatch, getState)一步,因为action不是一个函数,只是一个对象。也不会执行applyMiddleware中封装的dispacth方法。

dispatch解密

从以上函数调用栈的追踪可以解开dispatch的谜团了。

dispatch分为2种:

  • type BaseDispatch = (a: Action) => Action,由redux createStore.js提供的store.dispatch。处理结果返回action对象
  • type Dispatch = (a: Action AsyncAction) => any,由applyMiddleware封装的能够处理同步action和异步action的函数,并将返回值传递给其他中间件使用。他的返回值类型没有任何限制。

Base dispatch function 总是同步地把 action 与上一次从 store 返回的 state 发往 reducer,然后计算出新的 state。它期望 action 会是一个可以被 reducer 消费的普通对象。

Middleware 封装了 base dispatch function,允许 dispatch function 处理 普通action 之外的异步 action。 Middleware 可以改变、延迟、忽略 action 或异步 action,也可以在传递给下一个 middleware 之前对它们进行解释

上述例子中的情形一就是调用了Middleware封装的dispacth对action做了处理(虽然我没有真的写处理代码),而情形二是直接调用store实例的base dispatch.

action creator

这里要引入action creator的概念。

Action Creator 就是一个创建 action 的函数。Action 是一个信息的负载,而 action creator 是一个创建 action 的工厂。

调用 action creator 只会生产 action,但不分发。还需要调用 store 的 dispatch function 才会引起变化。

什么时候要在action creator调用dispatch

如果 action creator 需要读取当前的 state、调用 API、或引起诸如路由变化等副作用,那么它应该返回一个异步 action而不是 action。这时候就应该使用Middleware封装的dispacth了。

最常见的2种情形:

  • 需要获取state,通过读取state的内容结合view层传过来的参数生成action的内容,就需要使用Middleware封装的方法,得到getState接口来获得state,然后dispatch action

      function actionCreator(params) {
          (dispatch, getState) => {
              let state = getState();
              //获取state内容,并结合params等进行逻辑处理
              return dispatch({
                  type: types.TEST
                  data: data
              })
          }
      }
    
  • 异步action。譬如需要异步请求的结果作为action的内容,需要和Promise搭配使用等。

      function fetchData(data){
          fetch(url, {
              body: data
          })
          .then((response) => {
          	if(response.status >= 200 && response.status < 300){
                  return response.json()
              } else {
                  message.error("服务器错误,请联系技术哥哥");
              }
          })
          .then((response) => {
            return new Promise((resolve, reject) => {
                response && resolve(response);
            })
          })
          .catch(()=>{
            errorCallback();
          });
      }
    	
      export function asynchronousActionCreator(params){
    	
        return (dispatch, getState) => {
          let promiseRes;
    	    
          promiseRes = fetchData(params); // fetchData函数返回的是promise对象
          if(promiseRes && promiseRes.then){
            promiseRes.then((data)=>{ 
              return  dispatch({
                      type: types.CHANGE_DATA,
                      data: data
                    })    
          });
        }
      }