源码学习之: 手写redux

2020-05-08  本文已影响0人  风雅欢乐

createStore

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

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函数有两个参数:

它的功能是:

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中原始的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;
上一篇下一篇

猜你喜欢

热点阅读