`

redux中间件

 
阅读更多

1.redux中间件简介

使用中间件的目的,派发事件的过程中,处理事件执行前的state、装饰事件执行中的dispatch方法(包含处理事件执行中的action)、处理事件执行后的state。

redux-logger模块,打印事件执行前后的state、以及action、执行时间等。

 

2.redux中间件机制

同koa、express中间件相同,koa、express用于处理请求req、响应res,redux用于装饰dispatch派发事件方法。介于redux中间件处理的是state和dispatch,因此需要将store.getState、store.dispatch作为参数传入中间件当中。实际上,dispatch方法用于触发事件,想在dispatch方法前后添加执行函数,就必须采用函数式编程的方式将dispatch方法作为回调函数传入中间件中,前后经过装饰后,最终返回值为接受action作为参数的函数,同dispatch相同;再交给下一个中间件处理,同样做装饰后,返回接受action作为参数的函数。因此,各中间件装饰dispatch方法的顺序为自右向左,各中间件处理函数的执行顺序为自左向右,类似koa将各中间件的处理函数及dispatch方法结合为一个函数。

 

// applyMiddleware  
import compose from './compose'  
  
// 使用中间件装饰dispach方法  
export default function applyMiddleware(...middlewares) {  
    return (createStore) => (reducer, preloadedState, enhancer) => {  
        var store = createStore(reducer, preloadedState, enhancer)  
        var dispatch = store.dispatch  
        var chain = []  
  
        var middlewareAPI = {  
            getState: store.getState,  
            dispatch: (action) => dispatch(action)  
        }  
        // middleware中间件接受参数为包含getState、dispatch的对象,返回函数,该函数接受上一个中间件返回值作为参数  
        chain = middlewares.map(middleware => middleware(middlewareAPI))  
        dispatch = compose(...chain)(store.dispatch)  
  
        return {  
            ...store,  
            dispatch  
        }  
    }  
}  
  
  
  
// compose.js  
export default function (...funcs) {  
    if (funcs.length === 0) {  
        return arg => arg  
    }  
  
    // 只有一个中间件时,中间件先接受getState、dispatch作为参数,返回函数,该函数接受dispatch作为函数并装饰,随后又返回函数,接受action作为参数  
    if (funcs.length === 1) {  
        return funcs[0]  
    }  
  
    const last = funcs[funcs.length - 1]  
    const rest = funcs.slice(0, -1)  
  
    // 数组的reduceRight方法,自右向左遍历执行首参函数(该函数首参为前一次函数执行的结果),次参为初始化传入首参函数的composed  
    // reduce为自左向右遍历数组,调用首参函数逐次修改最终结果值并返回,供下一个首参函数使用  
    // redux中使用为middleware中间件数组作为参数funcs,dispatch作为参数args,最末一个中间件装饰dispacth方法后,交给上一个中间件装饰  
    //      具体功能实现为打印接收到的action和state变化,最终返回值接受action作为参数,完成派发事件的目的  
    return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))  
}  
 

 

3.redux-logger日志,打印state、action

中间件书写,预先配置中间件,其后接受getState方法用于打印事件触发前后的state(middleware(middlewareAPI)处理过程中),其后接受dispatch或下一个中间件装饰dispatch方法(logger中间件通常位于中间件的下游,因此最有可能直接接受dispatch方法),其后装饰函数接受action触发事件。

使用:

 

import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import promise from 'redux-promise';
import createLogger from 'redux-logger';

const logger = createLogger();
const store = createStore(
  reducer,
  applyMiddleware(thunk, promise, logger)
);

 

源码:

defaults.js
// redux-logger默认配置
export default {
    level: `log`,// console对象方法名,如log等
    logger: console,// 不同平台的console对象
    logErrors: true,// 使用try-catch方法装饰dispatch方法或各中间件装饰结果函数
    collapsed: undefined,// 打印日志时是否分层输出
    predicate: undefined,// 接受getState、action作为参数,返回否值时,表示logger中间件不作任何处理
    duration: false,// 事件执行时长
    timestamp: true,// 事件执行起始时间
    stateTransformer: state => state,// 打印前转化state
    actionTransformer: action => action,// 打印前转化action对象
    errorTransformer: error => error,// 打印前转化error对象
    colors: {// 日志输出颜色
        title: () => `inherit`,
        prevState: () => `#9E9E9E`,
        action: () => `#03A9F4`,
        nextState: () => `#4CAF50`,
        error: () => `#F20404`,
    },
    diff: false,// 是否比对前后两个state的差别并打印
    diffPredicate: undefined,// 函数式判断是否比对前后两个state的差别并打印

    // Deprecated options
    transformer: undefined,// 被取消的方法,使用stateTransformer代替
};



helpers.js
export const repeat = (str, times) => (new Array(times + 1)).join(str);

export const pad = (num, maxLength) => repeat(`0`, maxLength - num.toString().length) + num;

// 转化日期对象
export const formatTime = (time) => `${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:
	${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}`;

// H5 performance用于精确反应每段函数的处理时长,效果等同Date对象
export const timer = (typeof performance !== `undefined` && performance !== null) && 
	typeof performance.now === `function` ? performance : Date;



index.js
import { printBuffer } from './core';
import { timer } from './helpers';
import defaults from './defaults';

// createLogger接受options配置中间件,返回值传入applyMiddleware方法中挂载该中间件
function createLogger(options = {}) {
    const loggerOptions = {
      ...defaults,
      ...options,
    };

    const {
        logger,// console对象
        transformer,// 被取消的方法,使用stateTransformer代替
        stateTransformer,// 打印前转化state
        errorTransformer,// 打印前转化error对象
        predicate,// 接受getState、action作为参数,返回否值时,表示logger中间件不作任何处理
        logErrors,// 使用try-catch方法装饰dispatch方法或各中间件装饰结果函数
        diffPredicate,
    } = loggerOptions;

    // 不作任何处理,移交给下一个中间件
    if (typeof logger === `undefined`) {
        return () => next => action => next(action);
    }

    if (transformer) {
        console.error(`Option 'transformer' is deprecated, use 'stateTransformer' instead!`); // eslint-disable-line no-console
    }

    const logBuffer = [];// 数组形式存储待打印对象,闭包缓存

    return ({ getState }) => (next) => (action) => {
        if (typeof predicate === `function` && !predicate(getState, action)) {
            return next(action);
        }

        const logEntry = {};
        logBuffer.push(logEntry);

        logEntry.started = timer.now();// 事件开始时间
        logEntry.startedTime = new Date();
        logEntry.prevState = stateTransformer(getState());// 事件起始state状态,经过stateTransformer转化
        logEntry.action = action;// 事件接受的action

        let returnedValue;// 缓存logger中间件装饰函数
        if (logErrors) {
            try {
                returnedValue = next(action);
            } catch (e) {
                logEntry.error = errorTransformer(e);
            }
        } else {
            returnedValue = next(action);
        }

        logEntry.took = timer.now() - logEntry.started;// 事件处理占用的时长
        logEntry.nextState = stateTransformer(getState());// 事件结束的state状态,经过stateTransformer转化

        const diff = loggerOptions.diff && typeof diffPredicate === `function` ? 
            diffPredicate(getState, action) : loggerOptions.diff;

        // 打印action日志
        printBuffer(logBuffer, { ...loggerOptions, diff });

        logBuffer.length = 0;// 释放logBuffer

        if (logEntry.error) throw logEntry.error;
        return returnedValue;
    };
}

export default createLogger;



core.js
import { formatTime } from './helpers';
import diffLogger from './diff';

// 获取打印方法名等
function getLogLevel(level, action, payload, type) {
    switch (typeof level) {
        case `object`:
            return typeof level[type] === `function` ? level[type](...payload) : level[type];
        case `function`:
            return level(action);
        default:
            return level;
    }
}

// 一组输出文案的标题,对应一个事件,包含action.type,触发时间,执行时长
function defaultTitleFormatter(options) {
    const {
        timestamp, duration,
    } = options;

    return (action, time, took) => {
        const parts = [`action`];
        if (timestamp) {
            parts.push(`@ ${time}`);
        }
        parts.push(action.type);
        if (duration) {
            parts.push(`(in ${took.toFixed(2)} ms)`);
        }
        return parts.join(` `);
    };
}

// 打印日志
export function printBuffer(buffer, options) {
    const {
      logger,
      actionTransformer,
      titleFormatter = defaultTitleFormatter(options),// 获取一组日志文案的标题
      collapsed, colors, level, diff,
    } = options;

    buffer.forEach((logEntry, key) => {
        const { started, startedTime, action, prevState, error } = logEntry;
        let { took, nextState } = logEntry;
        const nextEntry = buffer[key + 1];

        if (nextEntry) {
            nextState = nextEntry.prevState;
            took = nextEntry.started - started;
        }

        const formattedAction = actionTransformer(action);
        const isCollapsed = (typeof collapsed === `function`) ? collapsed(() => nextState, action) : collapsed;

        const formattedTime = formatTime(startedTime);
        const titleCSS = colors.title ? `color: ${colors.title(formattedAction)};` : null;
        const title = titleFormatter(formattedAction, formattedTime, took);

        // console.log("%c text","color:red");
        // %c使用css风格设置样式,%s字符串替换,%d或%i数值替换
        // %f浮点型替换,%o替换为dom对象,%O替换为对象
        // console.log("%c text","color:red","string"); 将string作为最后字符插入
        // console.group、console.groupCollapsed分层打印内容,group展开,groupCollapsed折叠
        try {
            if (isCollapsed) {
                if (colors.title) logger.groupCollapsed(`%c ${title}`, titleCSS);
                else logger.groupCollapsed(title);
            } else {
                if (colors.title) logger.group(`%c ${title}`, titleCSS);
                else logger.group(title);
            }
        } catch (e) {
            logger.log(title);
        }

        const prevStateLevel = getLogLevel(level, formattedAction, [prevState], `prevState`);
        const actionLevel = getLogLevel(level, formattedAction, [formattedAction], `action`);
        const errorLevel = getLogLevel(level, formattedAction, [error, prevState], `error`);
        const nextStateLevel = getLogLevel(level, formattedAction, [nextState], `nextState`);

        if (prevStateLevel) {
            if (colors.prevState) logger[prevStateLevel](`%c prev state`, `color: ${colors.prevState(prevState)}; font-weight: bold`, prevState);
            else logger[prevStateLevel](`prev state`, prevState);
        }

        if (actionLevel) {
            if (colors.action) logger[actionLevel](`%c action`, `color: ${colors.action(formattedAction)}; font-weight: bold`, formattedAction);
            else logger[actionLevel](`action`, formattedAction);
        }

        if (error && errorLevel) {
            if (colors.error) logger[errorLevel](`%c error`, `color: ${colors.error(error, prevState)}; font-weight: bold`, error);
            else logger[errorLevel](`error`, error);
        }

        if (nextStateLevel) {
            if (colors.nextState) logger[nextStateLevel](`%c next state`, `color: ${colors.nextState(nextState)}; font-weight: bold`, nextState);
            else logger[nextStateLevel](`next state`, nextState);
        }

        // 比对前后两个state的差别并打印
        if (diff) {
            diffLogger(prevState, nextState, logger, isCollapsed);
        }

        try {
            logger.groupEnd();
        } catch (e) {
            logger.log(`—— log end ——`);
        }
    });
}



diff.js
// differ(objA,objB)比较objA、objB两对象,输出为改变状况
import differ from 'deep-diff';

// https://github.com/flitbit/diff#differences
const dictionary = {
    'E': {
        color: `#2196F3`,
        text: `CHANGED:`,
    },
    'N': {
        color: `#4CAF50`,
        text: `ADDED:`,
    },
    'D': {
        color: `#F44336`,
        text: `DELETED:`,
    },
    'A': {
        color: `#2196F3`,
        text: `ARRAY:`,
    },
};

function style(kind) {
    return `color: ${dictionary[kind].color}; font-weight: bold`;
}

function render(diff) {
    const { kind, path, lhs, rhs, index, item } = diff;

    switch (kind) {
        case `E`:
            return `${path.join(`.`)} ${lhs} → ${rhs}`;
        case `N`:
            return `${path.join(`.`)} ${rhs}`;
        case `D`:
            return `${path.join(`.`)}`;
        case `A`:
            return [`${path.join(`.`)}[${index}]`, item];
        default:
            return null;
    }
}

// 比对前后两个state的差别并打印
export default function diffLogger(prevState, newState, logger, isCollapsed) {
    const diff = differ(prevState, newState);

    try {
        if (isCollapsed) {
            logger.groupCollapsed(`diff`);
        } else {
            logger.group(`diff`);
        }
    } catch (e) {
        logger.log(`diff`);
    }

    if (diff) {
        diff.forEach((elem) => {
            const { kind } = elem;
            const output = render(elem);

            logger.log(`%c ${dictionary[kind].text}`, style(kind), output);
        });
    } else {
        logger.log(`—— no diff ——`);
    }

    try {
        logger.groupEnd();
    } catch (e) {
        logger.log(`—— diff end —— `);
    }
}

 

 3.redux-thunk函数式编程,将dispatch方法作为参数传入,用于触发另一个异步事件

与redux-logger大迥异的是,redux-logger通过向dispatch传入action触发实际事件,redux-thunk接受的是函数,第一时间并未触发事件,只是在装饰过程中,函数体内又获得store.dispatch方法,用于触发异步事件,除此以外,均为待执行函数,与redux式的事件机制无关,返回值也与redux无关,由thunk中间件传参函数构造,若为promise对象,可以链式调用promise方法。

使用:https://github.com/gaearon/redux-thunk

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

// Note: this API requires redux@>=3.1.0
const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);

function fetchSecretSauce() {
  return fetch('https://www.google.com/search?q=secret+sauce');
}

// These are the normal action creators you have seen so far.
// The actions they return can be dispatched without any middleware.
// However, they only express “facts” and not the “async flow”.

function makeASandwich(forPerson, secretSauce) {
  return {
    type: 'MAKE_SANDWICH',
    forPerson,
    secretSauce
  };
}

function apologize(fromPerson, toPerson, error) {
  return {
    type: 'APOLOGIZE',
    fromPerson,
    toPerson,
    error
  };
}

function withdrawMoney(amount) {
  return {
    type: 'WITHDRAW',
    amount
  };
}

// Even without middleware, you can dispatch an action:
store.dispatch(withdrawMoney(100));

// But what do you do when you need to start an asynchronous action,
// such as an API call, or a router transition?

// Meet thunks.
// A thunk is a function that returns a function.
// This is a thunk.

function makeASandwichWithSecretSauce(forPerson) {

  // Invert control!
  // Return a function that accepts `dispatch` so we can dispatch later.
  // Thunk middleware knows how to turn thunk async actions into actions.

  return function (dispatch) {
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop', forPerson, error))
    );
  };
}

// Thunk middleware lets me dispatch thunk async actions
// as if they were actions!

store.dispatch(
  makeASandwichWithSecretSauce('Me')
);

// It even takes care to return the thunk’s return value
// from the dispatch, so I can chain Promises as long as I return them.

store.dispatch(
  makeASandwichWithSecretSauce('My wife')
).then(() => {
  console.log('Done!');
});

 源码:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics