美文网首页
源码学习之: 手写redux

源码学习之: 手写redux

作者: 风雅欢乐 | 来源:发表于2020-05-08 15:32 被阅读0次

    createStore

    createStore函数返回一个对象, 该对象包含属性:

    • dispatch: 分发一个action
    • getState: 得到仓库中当前的状态
    • replaceReducer: 替换掉当前的reducer(没什么用, 不实现)
    • subscribe: 注册一个监听器, 监听器是一个无参函数, 分发一个action后, 会运行注册的监听器. 该函数返回一个函数, 用于取消监听
    • Symbol("observable")(observable还是提案, 不实现)

    createStore函数代码

    import ActionTypes from './utils/actionTypes';
    import { isPlainObject } from './utils/isPlainObject';
    
    /**
     * 实现createStore功能
     * @param {function} reducer reducer函数
     * @param {*} defaultState 默认的状态值
     */
    export default function (reducer, defaultState) {
    
        let currentReducer = reducer;             // 当前的reducer
        let currentState = defaultState;          // 当前仓库中的状态
    
        const listeners = [];                     // 记录所有的监听器
    
        function dispatch(action) {
            // 验证action
            if (!isPlainObject(action)) {
                throw new TypeError("action must be a plain object");
            }
            // 验证action的type属性是否存在
            if (action.type === undefined) {
                throw new TypeError("action must have a 'type' property");
            }
    
            // 进行状态更新
            currentState = currentReducer(currentState, action);
    
            // 触发监听器
            for (const listener of listeners) {
                listener();
            }
        }
    
        function getState() {
            return currentState;
        }
    
        function subscribe(listener) {
            listeners.push(listener);
    
            let isremove = false;
            return function () {
                if (isremove) {
                    return;
                }
                // 将listener从数组中移除
                const index = listeners.indexOf(listener);
                listeners.splice(index, 1);
                isremove = true;
            }
        }
    
        // 创建仓库时, 需要分发一个特殊的action. 完成状态的初始化
        dispatch({
            type: ActionTypes.INIT()
        });
    
        return {
            dispatch,
            getState,
            subscribe,
        }
    }
    
    // isPlainObject.js文件
    
    /**
     * 判断一个对象是否是平面对象
     * @param {*} obj 
     */
    export function isPlainObject(obj) {
        if (typeof obj !== 'object') {
            return false;
        }
    
        return Object.getPrototypeOf(obj) === Object.prototype;
    }
    
    // actionTypes.js文件
    
    /**
     * 得到一个指定长度的随机字符串
     * @param {*} length 
     */
    function getRandomString(length) {
        return Math.random().toString(36).substr(2, length);
    }
    
    export default {
        INIT() {
            return `@@redux/INIT${getRandomString(6).split("").join(".")}`
        },
        UNKNOWN() {
            return `@@redux/PROBE_UNKNOWN_ACTION${getRandomString(6).split("").join(".")}`
        }
    }
    

    bindActionCreators

    bindActionCreators函数有两个参数:

    • actionCreators: action创建函数
    • dispatch: store的dispatch函数

    它的功能是:

    • 如果actionCreators是一个函数, 那么返回一个新的函数, 这个新的函数需要的参数和actionCreators函数一样, 但是会自动dispatch
    • 如果actionCreators是一个对象, 并且这个对象的每个属性的值, 都是一个action创建函数, 那么返回一个对象. 返回的对象属性跟之前的一模一样, 但是属性值变为增强后的action创建函数, 会自动dispatch

    bindActionCreators代码

    
    export default function (actionCreators, dispatch) {
        if (typeof actionCreators === 'function') {
            return getAutoDispatchActonCreator(actionCreators, dispatch);
        } else if (typeof actionCreators === 'object') {
            const result = {};
            for (const key in actionCreators) {
                if (actionCreators.hasOwnProperty(key)) {
                    const actionCreator = actionCreators[key];
                    // 只有当传入的这个对象的属性值是个函数的时候, 才进行增强
                    // 否则什么都不做
                    if (typeof actionCreator === 'function') {
                        result[key] = getAutoDispatchActonCreator(actionCreator, dispatch);
                    }
                }
            }
            return result;
        } else {
            throw new TypeError("actionCreators must be an object or function");
        }
    }
    
    /**
     * 得到一个自动分发的action创建函数
     * @param {*} actionCreator 
     * @param {*} dispatch 
     */
    function getAutoDispatchActonCreator(actionCreator, dispatch) {
        return function (...args) {
            const action = actionCreator(...args)
            dispatch(action);
        }
    }
    

    combineReducers

    combineReducers组装多个reducers, 返回一个reducer, 数据使用一个对象表示, 对象的属性名与传递的参数对象保持一致.

    即参数是一个对象, 对象的每个属性的值都是一个reducer函数, 返回一个函数(reducer函数), 这个reducer的状态是一个对象, 此对象的属性对应着参数对象的属性.

    import { isPlainObject } from './utils/isPlainObject';
    import actionTypes from './utils/actionTypes';
    
    
    export default function (reducers) {
        // 1. 验证输入参数
        validateReducers(reducers);
    
        // 返回的是一个reducer函数
        return function (state = {}, action) {
            const newState = {};
            for (const key in reducers) {
                if (reducers.hasOwnProperty(key)) {
                    const reducer = reducers[key];
                    newState[key] = reducer(state[key], action);
                }
            }
            return newState;
        }
    }
    
    
    function validateReducers(reducers) {
        if (typeof reducers !== 'object') {
            throw new TypeError("parameter of combineReducers must be an object");
        }
        if (!isPlainObject(reducers)) {
            throw new TypeError("parameter of combineReducers must be a plain object");
        }
        // 验证reducer的返回结果是不是undefined
        // 派发两个特殊的action, 目的就是要确保每个reducer函数, 在switch判断匹配不到的时候, 会原封不动的返回原来的状态
        // 即确保每个reducer函数写了switch代码段的default处理情况.
        // 因为这两个action的type值很特殊, 一般的reducer肯定不会匹配上, 肯定会进入到switch的default处理分支.
        // 而每个reducer都有默认的state值, 如果验证传入的参数是undefined, 那么会使用默认值, 不应该返回undefined
        for (const key in reducers) {
            if (reducers.hasOwnProperty(key)) {
                const reducer = reducers[key];
                // 传递特殊type值
                // reducer有默认值, 本次调用传入undefined参数, 就会使用默认值
                let state = reducer(undefined, { type: actionTypes.INIT() });
                if (state === undefined) {
                    throw new TypeError("reducers must not return undefined");
                }
                state = reducer(undefined, { type: actionTypes.UNKNOWN() });
                if (state === undefined) {
                    throw new TypeError("reducers must not return undefined");
                }
            }
        }
    }
    

    applyMiddleware

    redux的中间件: 中间件实际上是对store里dispatch函数的增强

    • 本身是一个函数, 该函数接收一个store参数, 表示创建的仓库, 该仓库并非一个完整的仓库对象, 仅包含getState, dispatch. 该函数运行的时间, 是在仓库创建之后

      • 由于创建仓库后需要自动运行设置的中间件参数, 因此, 需要在创建仓库时告诉仓库有哪些中间件
      • 需要调用applyMiddleware函数, 将函数的返回结果作为createStore的第二或者第三个参数
    • 中间件函数必须返回一个dispatch创建函数

    • applyMiddleware函数, 用于记录有哪些中间件, 它会返回一个函数

      • 该函数用于记录创建仓库的方法, 然后又返回一个函数
        • 返回的函数用来创建仓库

    中间件按照注册的顺序, 最后一个中间件传入store中原始的dispatch, 经过增强后返回新的dispatch传入倒数第二个中间件, 以此类推. 最终, 第一个中间件返回的dispatch函数, 重新赋值给store的dispatch属性.

    QQ浏览器截图20200510135004.png

    applyMiddleware代码

    // applyMiddleware.js文件
    
    import compose from './compose';
    
    /**
     * 注册中间件函数
     * @param  {...any} middlewares 所有中间件
     */
    export default function (...middlewares) {
        // 返回的函数接收创建仓库的方法
        return function (createStore) {
            // 再次返回的函数接收reducer和defaultState实际创建仓库
            return function (reducer, defaultState) {
                // 创建仓库
                const store = createStore(reducer, defaultState);
                // dispatch赋值为这个函数的目的是, 让开发者在dispatch完成替换前不要调用
                let dispatch = () => {
                    throw new Error("目前还不能使用dispatch");
                };
    
                // 给dispatch赋值, 替换dispatch
                // 根据中间件数组, 得到一个dispatch创建函数的数组
                const simpleStore = {
                    getState: store.getState,
    
                    // 不能使用下面被注释的这种写法, 这种写法的dispatch指向最原始的store的dispatch
                    // dispatch: store.dispatch,
                    // 而且也不能如下面注释这样写, 这样写的话会一直指向抛出错误的那个函数
                    // dispatch: dispatch,
                    dispatch: (...args) => dispatch(...args),
                };
                const dispatchProducers = middlewares.map(mid => mid(simpleStore));
                const dispatchProducer = compose(...dispatchProducers);
                dispatch = dispatchProducer(store.dispatch);
    
                return {
                    ...store,
                    dispatch,
                }
            }
        }
    }
    
    // compose.js文件
    
    export default function compose(...funcs) {
        // 如果没有函数要组合, 则返回的函数原封不动的返回参数
        if (funcs.length === 0) {
            return args => args;
        } else if (funcs.length === 1) {
            return funcs[0];
        } else {
            return funcs.reduce((a, b) => (...args) => a(b(...args)));
        }
    }
    

    redux中间件: thunk

    thunk中间件: 如果给thunk中间件的是action是一个函数, 那么thunk会运行该函数. 如果不是函数, 则向后移交action

    thunk代码

    function createThunkMiddleware(extra) {
        // 返回一个thunk中间件
    
        return store => next => action => {
            if (typeof action === 'function') {
                return action(store.dispatch, store.getState, extra);
            } else {
                return next(action);
            }
        }
    }
    
    const thunk = createThunkMiddleware();
    thunk.withExtraArgument = createThunkMiddleware;
    
    export default thunk;
    

    相关文章

      网友评论

          本文标题:源码学习之: 手写redux

          本文链接:https://www.haomeiwen.com/subject/mcjfnhtx.html