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 小时前
婚纱摄影网站|基于ssm + vue婚纱摄影网站系统(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot·后端
C_心欲无痕6 小时前
ts - tsconfig.json配置讲解
linux·前端·ubuntu·typescript·json
清沫6 小时前
Claude Skills:Agent 能力扩展的新范式
前端·ai编程
yinuo7 小时前
前端跨页面通信终极指南:方案拆解、对比分析
前端
yinuo7 小时前
前端跨页面通讯终极指南⑨:IndexedDB 用法全解析
前端
xkxnq8 小时前
第二阶段:Vue 组件化开发(第 16天)
前端·javascript·vue.js
烛阴8 小时前
拒绝配置地狱!5 分钟搭建 Three.js + Parcel 完美开发环境
前端·webgl·three.js
Van_Moonlight8 小时前
RN for OpenHarmony 实战 TodoList 项目:空状态占位图
javascript·开源·harmonyos
xkxnq8 小时前
第一阶段:Vue 基础入门(第 15天)
前端·javascript·vue.js
anyup10 小时前
2026第一站:分享我在高德大赛现场学到的技术、产品与心得
前端·架构·harmonyos