Skip to content

Latest commit

 

History

History
205 lines (164 loc) · 7.07 KB

使用 Redux 打造你的应用 —— middleware.md

File metadata and controls

205 lines (164 loc) · 7.07 KB

使用 Redux 打造你的应用 —— middleware

applyMiddleware

我们先来复习一下 redux 的 middleware,以及 applyMiddleware 对中间件进行的串联处理。

源码解读:

// 定义一个代码组合的方法
// 传入一些function作为参数,返回其链式调用的形态。例如,
// compose(f, g, h) 最终返回 (...args) => f(g(h(...args)))
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))
  }
}

export default function applyMiddleware(...middlewares) {
  // 最终返回一个以createStore为参数的匿名函数
  // 这个函数返回另一个以reducer, initialState, enhancer为参数的匿名函数
  return (createStore) => (reducer, initialState, enhancer) => {
    var store = createStore(reducer, initialState, enhancer)
    var dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    // 每个 middleware 都以 middlewareAPI 作为参数进行注入调用,返回一个新的链。此时的返回值相当于调用 thunkMiddleware 返回的函数: (next) => (action) => {} ,接收一个next作为其参数
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 并将链代入进 compose 组成一个函数的调用链
    // compose(...chain) 返回形如(...args) => f(g(h(...args))),f/g/h都是chain中的函数对象。
    // 之后以 store.dispatch 作为参数进行注入
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

applyMiddleware 内部,传入的所有 middleware 通过 map 进行遍历,每个 middleware 都以 { getState: store.getState, dispatch: (action) => dispatch(action) } 为参数进行一次调用。在这之后,通过 compose 方法进行代码组合:

// 传入一些function作为参数,返回其链式调用的形态。例如,
// compose(f, g, h) 最终返回 (...args) => f(g(h(...args)))
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))
  }
}

所有的 middleware 形成一个层层包含的调用链。然后再以 store.dispatch 为参数进行调用。每一层 middleware 的调用,都会返回一个新的、被包裹封装了的 dispatch 函数(在每一层的封装里,middleware 可以调用到 actiongetState API,以此做到自己想做的事情 ),最终返回一个新的 dispatch

dispatch = compose(...chain)(store.dispatch) // 这个 dispatch 作为最终 redux 应用的 dispatch 函数

由此可见,在通过 applyMiddleware 封装了 middleware 之后,每次 dispatch(someAction) ,会以此把 action 传入各个 middleware 中,middleware 可以做自己想做的操作,比如输出 log,调用 promise 等。

middleware

由上可见,middleware 的规范是有一定限制。

const middleware = ({ dispatch, getState }) =>
	(next) => (action) => {
      // do something
      return next(action);
	}

next 其实就是被改变过的 dispatch 函数。在每个 middleware 中,可以收到用户分发的 action 函数;进行完自己的操作以后,需要通过 next 继续将 action 分发下去。

因此,你可以自己尝试写一个简单的 输出 log 的 middleware:

const logger = ({ getState }) => (next) => (action) => {
  console.log(getState());
  return next(action);
};

然后在 applyMiddleware 中进行注入:

const AppStore = createStore(appReducer, initialState, applyMiddleware(logger));

这样,每当应用 dispatch action 的时候,都会输出当前的 state。

关于 middleware 的更多阅读资料:

redux-promise

redux-promise 是一个相当简洁的小库,它帮助你完成异步请求以后自动分发到 reducer,并且在失败(reject)的时候停止继续分发。

// 注入 middleware
import promiseMiddleware from 'redux-promise';

const AppStore = createStore(appReducer, initialState, applyMiddleware(promiseMiddleware));
// actions.js
// 在 action 中使用异步
import API from 'some api func';

// API.getThing 返回的应该是个 promise,并且需要直接将结果代入 reducer
export const getThing = createAction('GET_THING', API.getThing);

// 相当于:
const getThing = createAction('GET_THING');
const fetchThing = () => (dispatch, getState) => {
  API.getThing.then(result => dispatch(getThing(result)));
};

由此可见,在使用了 redux-promise 之后,在通过 createAction 创建 action 时可以直接代入 promise 。也就是说,redux-promise 中间件在其内部帮我们完成了 promise.then 的调用,并将最终结果传递给了 action.payload

redux-promise 源码:

import { isFSA } from 'flux-standard-action';

function isPromise(val) {
  return val && typeof val.then === 'function';
}

export default function promiseMiddleware({ dispatch }) {
  return next => action => {
    // 对于非 FSA 的 action 而言,如果 action 是个 Promise,则调用 then 方法,并把 dispatch 作为参数代入
    if (!isFSA(action)) {
      return isPromise(action)
        ? action.then(dispatch)
        : next(action);
    }
	// 否则,这个 action 是一个标准 action,返回值类似:
    // {
    //  type: '',
    //	payload: '',
    //	error: bool
    // }
    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);
  };
}

顺便看一眼 flux-standard-action

import isPlainObject from 'lodash.isplainobject';
import isString from 'lodash.isstring';
import isSymbol from 'lodash.issymbol';

export function isFSA(action) {
  return (
    // action 必须是一个 plan object
    isPlainObject(action) &&
    // type 是 String 或者 Symbol
    (isString(action.type) || isSymbol(action.type)) &&
    // action object 的每一个 key 都是合法的,只存在于 ['type', 'payload', 'error', 'meta'] 中
    Object.keys(action).every(isValidKey)
  );
}

export function isError(action) {
  return action.error === true;
}

function isValidKey(key) {
  return [
    'type',
    'payload',
    'error',
    'meta',
  ].indexOf(key) > -1;
}