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(".");
}

以上。

相关推荐
我不只是切图仔20 分钟前
我只是想给网站加个注册验证码,咋就那么难!
前端·后端
JohnYan42 分钟前
工作笔记 - 微信消息发送和处理
javascript·后端·微信
该用户已不存在1 小时前
macOS是开发的终极进化版吗?
前端·后端
小豆包api1 小时前
小豆包AI API × Nano Banana:3D手办 + AI视频生成,「动起来」的神级玩法!
前端·api
陈陈爱java1 小时前
Spring八股文
开发语言·javascript·数据库
布列瑟农的星空1 小时前
大话设计模式——观察者模式和发布/订阅模式的区别
前端·后端·架构
龙在天1 小时前
Vue3 实现 B站 视差 动画
前端
KenXu1 小时前
F2C Prompt to Design、AI 驱动的设计革命
前端
小鱼儿亮亮1 小时前
canvas中画线条,线条效果比预期宽1像素且模糊问题分析及解决方案
前端·react.js
@大迁世界2 小时前
用 popover=“hint“ 打造友好的 HTML 提示:一招让界面更“懂人”
开发语言·前端·javascript·css·html