From c2f5f064179c065a47045376f724b290e77890e0 Mon Sep 17 00:00:00 2001 From: ecmadao Date: Mon, 30 Jan 2017 10:05:52 +0800 Subject: [PATCH] redux middleware --- ...20\347\240\201\350\247\243\346\236\220.md" | 2 +- ...50 \342\200\224\342\200\224 middleware.md" | 205 ++++++++++++++++++ 2 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 "Notes/React/Redux/\344\275\277\347\224\250 Redux \346\211\223\351\200\240\344\275\240\347\232\204\345\272\224\347\224\250 \342\200\224\342\200\224 middleware.md" diff --git "a/Notes/React/Redux/Redux\345\205\245\345\235\221\350\277\233\351\230\266-\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/Notes/React/Redux/Redux\345\205\245\345\235\221\350\277\233\351\230\266-\346\272\220\347\240\201\350\247\243\346\236\220.md" index 1bf3a06..01c192b 100644 --- "a/Notes/React/Redux/Redux\345\205\245\345\235\221\350\277\233\351\230\266-\346\272\220\347\240\201\350\247\243\346\236\220.md" +++ "b/Notes/React/Redux/Redux\345\205\245\345\235\221\350\277\233\351\230\266-\346\272\220\347\240\201\350\247\243\346\236\220.md" @@ -361,7 +361,7 @@ export default function thunkMiddleware({ dispatch, getState }) { ```javascript // usage -import {createStore, applyMiddleware} from 'redux'; +import { createStore, applyMiddleware } from 'redux'; import thunkMiddleware from 'redux-thunk'; const store = createStore( diff --git "a/Notes/React/Redux/\344\275\277\347\224\250 Redux \346\211\223\351\200\240\344\275\240\347\232\204\345\272\224\347\224\250 \342\200\224\342\200\224 middleware.md" "b/Notes/React/Redux/\344\275\277\347\224\250 Redux \346\211\223\351\200\240\344\275\240\347\232\204\345\272\224\347\224\250 \342\200\224\342\200\224 middleware.md" new file mode 100644 index 0000000..4bd3a8d --- /dev/null +++ "b/Notes/React/Redux/\344\275\277\347\224\250 Redux \346\211\223\351\200\240\344\275\240\347\232\204\345\272\224\347\224\250 \342\200\224\342\200\224 middleware.md" @@ -0,0 +1,205 @@ +## 使用 Redux 打造你的应用 —— middleware + +### `applyMiddleware` + +我们先来复习一下 redux 的 `middleware`,以及 `applyMiddleware` 对中间件进行的串联处理。 + +源码解读: + +```javascript +// 定义一个代码组合的方法 +// 传入一些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` 方法进行代码组合: + +```javascript +// 传入一些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 可以调用到 `action`、`getState` API,以此做到自己想做的事情 ),最终返回一个新的 `dispatch`: + +```javascript +dispatch = compose(...chain)(store.dispatch) // 这个 dispatch 作为最终 redux 应用的 dispatch 函数 +``` + +由此可见,在通过 `applyMiddleware` 封装了 middleware 之后,每次 `dispatch(someAction)` ,会以此把 action 传入各个 middleware 中,middleware 可以做自己想做的操作,比如输出 log,调用 promise 等。 + +### middleware + +由上可见,middleware 的规范是有一定限制。 + +```javascript +const middleware = ({ dispatch, getState }) => + (next) => (action) => { + // do something + return next(action); + } +``` + +`next` 其实就是被改变过的 `dispatch` 函数。在每个 middleware 中,可以收到用户分发的 `action` 函数;进行完自己的操作以后,需要通过 `next` 继续将 `action` 分发下去。 + +因此,你可以自己尝试写一个简单的 输出 log 的 middleware: + +```javascript +const logger = ({ getState }) => (next) => (action) => { + console.log(getState()); + return next(action); +}; +``` + +然后在 `applyMiddleware` 中进行注入: + +```javascript +const AppStore = createStore(appReducer, initialState, applyMiddleware(logger)); +``` + +这样,每当应用 dispatch action 的时候,都会输出当前的 state。 + +关于 middleware 的更多阅读资料: + +- [A Beginner's Guide to Redux Middleware](https://www.codementor.io/vkarpov/beginner-s-guide-to-redux-middleware-du107uyud) +- [Middleware -- redux doc](http://redux.js.org/docs/advanced/Middleware.html) + +### redux-promise + +[redux-promise](https://github.com/acdlite/redux-promise) 是一个相当简洁的小库,它帮助你完成异步请求以后自动分发到 reducer,并且在失败(reject)的时候停止继续分发。 + +```javascript +// 注入 middleware +import promiseMiddleware from 'redux-promise'; + +const AppStore = createStore(appReducer, initialState, applyMiddleware(promiseMiddleware)); +``` + +```javascript +// 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 源码: + +```javascript +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`](https://github.com/acdlite/flux-standard-action): + +```javascript +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; +} +``` +