React@16.x(60)Redux@4.x(9)- 实现 applyMiddleware

目录

接上篇文章:Redux中间件介绍

1,applyMiddleware 原理

Redux 应用中间件方式:

js 复制代码
const store = createStore(reducer, applyMiddleware(logger1, logger2));

实际会转为下面的方式执行:

js 复制代码
const store = applyMiddleware(logger1, logger2)(createStore)(reducer)
  • applyMiddleware 会确定用到的中间件;它会返回一个用来创建仓库的函数A,参数createStore
  • 函数 A,会返回创建仓库的函数B,和 createStore 函数差不多。
  • 函数B,会对中间件函数做处理,并修改原始 dispatch

大致相当于:

js 复制代码
export const applyMiddleware = (...middlewares) => {
	// 用来创建的仓库的函数
    return (createStore) => {
    	// 创建仓库的函数
        return (reducer, defaultState) => {
            const store = createStore(reducer, defaultState)
            const dispatch = '经过 middlewares 处理的 store.dispatch'
            return {
                ...store,
                dispatch
            }
        }
    }
}

2,实现

2.1,applyMiddleware

上篇文章介绍了 中间件函数 的写法和多个中间件函数的执行顺序。

基于此,实现这个 dispatch 流转的逻辑,并得到最终的 dispatch 即可完成 applyMiddleware

2.1.1,compose 方法

关键要实现

js 复制代码
const resultDispatch = logger1(logger2(logger3(store.dispatch)))

实现:

js 复制代码
/**
 * @param  {...any} funcs
 * @returns {function}
 */
export const compose = (...funcs) => {
    if (funcs.length === 0) {
        return (args) => args;
    } else if (funcs.length === 1) {
        return funcs[0];
    }
    return (...args) => {
        let lastReturn = null;
        for (let i = funcs.length - 1; i >= 0; i--) {
            const func = funcs[i];
            if (i === funcs.length - 1) {
                lastReturn = func(...args);
            } else {
                lastReturn = func(lastReturn);
            }
        }
        return lastReturn;
    };
};
js 复制代码
// 测试代码
const add = (n) => {
    return n + n;
};

const mult = (n) => {
    return n * n;
};

const b = compose(add, mult);
console.log(b(3)); // 先乘后加,18

可使用 Array.reduce 简化:

js 复制代码
export const compose = (...funcs) =>
    funcs.reduce(
        (prev, next) =>
            (...args) =>
                prev(next(...args))
    );

2.1.2,applyMiddleware

js 复制代码
import { compose } from "./compose";
export const applyMiddleware = (...middlewares) => {
    return (createStore) => {
        return (reducer, defaultState) => {
            const store = createStore(reducer, defaultState);
            let dispatch = () => {
                throw new Error("目前还不能使用 dispatch");
            };

            // 传递给中间件函数的 store 只有这2个属性。
            const simpleStore = {
                getState: store.getState,
                dispatch: (...args) => dispatch(...args), // 每个中间件函数的 dispatch 都是上一个中间件组装后的
            };
            // 获取用于创建 dispatch 的函数
            const dispatchProducts = middlewares.map((m) => m(simpleStore));
            // 重新组装后的 dispatch
            dispatch = compose(...dispatchProducts)(store.dispatch);
            return {
                ...store,
                dispatch,
            };
        };
    };
};

2.2,修改 createStore

之前实现的 createStore,没有对可能的第3个函数做处理。这里补充下:

  • 如果第2个参数是 applyMiddleware,那说明没有 defaultState

这里就简单判断写第2个参数是不是函数,实际源码中 defaultState 也可以通过一个函数创建。

js 复制代码
export const createStore = (reducer, defaultState, enhancer) => {
    // enhancer 表示 applymiddleware 返回的函数。
    if (typeof defaultState === 'function') {
        enhancer = defaultState
        defaultState = undefined
    }
    if (typeof enhancer === 'function') {
        enhancer(createStore)(reducer, defaultState)
    }
    // 其他剩余代码没有做变动。
    // ...
}

完整代码

js 复制代码
export const createStore = (reducer, defaultState, enhancer) => {
    // enhancer 表示 applymiddleware 返回的函数。
    if (typeof defaultState === "function") {
        enhancer = defaultState;
        defaultState = undefined;
    }
    if (typeof enhancer === "function") {
        enhancer(createStore)(reducer, defaultState);
    }
    
    let currentReducer = reducer;
    let currentState = defaultState;
    let listeners = [];

    const dispatch = (action) => {
        if (typeof action !== "object" || Object.getPrototypeOf(action) !== Object.prototype) {
            throw new Error("action 必须是一个 plain Object");
        }
        if (action.type === undefined) {
            throw new Error("action 必须有 type 属性");
        }
        currentState = currentReducer(currentState, action);
        // 每次更新时,遍历监听器。
        for (const listener of listeners) {
            listener();
        }
    };

    const getState = () => {
        return currentState;
    };

    const subscribe = (listener) => {
        listeners.push(listener);
        let isRemove = false;
        // 取消监听
        return () => {
            if (isRemove) {
                return;
            } else {
                isRemove = true;
                listeners = listeners.filter((f) => f !== listener);
            }
        };
    };

    // createStore 创建时会调用一次。
    dispatch({
        type: `@@redux/INIT${getRandomString}`,
    });

    return {
        dispatch,
        getState,
        subscribe,
    };
};

function getRandomString() {
    return Math.random().toString(36).substring(2, 8).split("").join(".");
}

以上。

相关推荐
陈大爷(有低保)6 分钟前
JS基础语法
开发语言·javascript·ecmascript
不修×蝙蝠18 分钟前
Javascript应用(TodoList表格)
前端·javascript·css·html
加勒比海涛1 小时前
ElementUI 布局——行与列的灵活运用
前端·javascript·elementui
你不讲 wood1 小时前
postcss 插件实现移动端适配
开发语言·前端·javascript·css·vue.js·ui·postcss
前端小程1 小时前
使用vant UI实现时间段选择
前端·javascript·vue.js·ui
whyfail2 小时前
React 事件系统解析
前端·javascript·react.js
禾苗种树2 小时前
element form rules 验证数组对象属性时如何写判断规则
javascript·vue.js·elementui
liangshanbo12152 小时前
JavaScript 中的一些常见陷阱
开发语言·javascript·ecmascript
小tenten3 小时前
js延迟for内部循环方法
开发语言·前端·javascript
幻影浪子3 小时前
Web网站常用测试工具
前端·测试工具