如何实现一个redux状态管理库

1,004 阅读6分钟

在前端开发中,状态管理是构建可维护、可扩展应用程序的关键方面之一。redux,作为一种流行的状态管理库,被广泛应用于React等框架中。 今天我们就自己实现一个redux

redux的核心概念是store(包含state, dispatch),action,reducer, 可以通过下面这张图表面他们之间的关系

1_EV1KQifr5KwiupJ9q7tdxA.png

createStore

接下来我们就根据一个简单的demo来实现redux

const initState = {
    post: 1
}

function reducer(state=initState, action) {
    switch(action.type) {
        case 'INCREASE_POST':
            return {...state, post: state.post + action.count};
        case 'DECREASE_POST':
            return {...state, post: state.post - action.count};
        default:
            return state;
    }
}

let store = createStore(reducer);

store.subscribe(() => {
    console.log(store.getState());
})

store.dispatch({type: 'INCREASE_POST', count: 1});
store.dispatch({type: 'DECREASE_POST', count: 1});
store.dispatch({type: 'INCREASE_POST', count: 1}); 

可以看到通过createStore方法会创建一个store对象,store对象有3个方法 dispatch派发一个action来修改state subscribe用来订阅state的改动 getState返回最新的state 其实这就是一个发布订阅模式的实现,逻辑其实很简单

function createStore(reducer) {
    let state;
    const listeners = [];

    function getState() {
        return state;
    }

    function dispatch(action) {
        state = reducer(state, action);
        listeners.forEach((listener) => {
            listener();
        })
    }

    function subscribe(listener) {
        listeners.push(listener);
    }

    return {
        getState,
        dispatch,
        subscribe
    }
}

我们用我们自己写的redux运行一下上面的Demo,输入如下

basic.png

combineReducers

实际开发的时候往往有多个state, 每个state又有自己reducer,这时候我们就需要combineReducers把多个reducer合并成一个reducer 同样我们先写一个简单的demo, 根据demo来实现combineReducers方法

const initPostState = {
    post: 1
}

function postReducer(state=initPostState, action) {
    switch(action.type) {
        case 'INCREASE_POST':
            return {...state, post: state.post + action.count};
        case 'DECREASE_POST':
            return {...state, post: state.post - action.count};
        default:
            return state;
    }
}

const initCommentState = {
    comment: 6
}

function commentReducer(state=initCommentState, action) {
    switch(action.type) {
        case 'INCREASE_COMMENT':
            return {...state, comment: state.comment + action.count};
        case 'DECREASE_COMMENT':
            return {...state, comment: state.comment - action.count};
        default:
            return state;
    }
}

const reducer = combineReducers({postState: postReducer, commentState: commentReducer});


let store = createStore(reducer);

store.subscribe(() => {
    console.log(store.getState());
})

store.dispatch({type: 'INCREASE_POST', count: 1});
store.dispatch({type: 'DECREASE_POST', count: 1});
store.dispatch({type: 'INCREASE_POST', count: 1}); 

store.dispatch({type: 'INCREASE_COMMENT', count: 1});
store.dispatch({type: 'DECREASE_COMMENT', count: 1});
store.dispatch({type: 'INCREASE_COMMENT', count: 1}); 

combineReducers接收一个对象,对象的key是存到state中的key,value是相应的reducer, combineReducers方法要返回一个方法,返回的方法里面会调用传进来的reducer,然后return一个处理好的state,这样createStore里面的逻辑就完全不用修改 OK 我们的实现如下

function combineReducers(reducers) {
    return function(state = {}, action) {
        const newState = {}
        Object.keys(reducers).forEach((key) => {
            newState[key] = reducers[key](state[key], action)
        })
        return newState;
    }
}

我们测试一下输出如下

combineReducers.png

applyMiddleware

接下来实现redux插件体系的核心方法applyMiddleware,还是先看下面的demo,demo里面定义了两个中间件logger1和logger2

function logger1(store) {
    return function(next) {
      return function(action) {
        console.info('before dispatching logger1', action);
        let result = next(action);
        console.log('after dispatching logger1', action);
        return result
      }
    }
  }
  
  function logger2(store, next) {
    return function(next) {
      return function(action) {
        console.info('before dispatching logger2', action);
        let result = next(action);
        console.log('after dispatching logger2', action);
        return result
      }
    }
  }

// 使用combineReducers组合两个reducer
const reducer = combineReducers({milkState: milkReducer, riceState: riceReducer});

let store = createStore(reducer, applyMiddleware(logger1, logger2));

可以看到createStore会接收applyMiddleware调用以后的返回值,这个返回值在redux中称作enhancer,他的作用就和他的名字一样 是用来加强createStore,enhancer是一个方法,这个方法会接收createStore,返回一个加强以后的createStore,我们调用加强以后 的createStore会返回一个新的store对象, 我们先按照这个逻辑改造一下createStore方法

function createStore(reducer, enhancer) {
    let state;
    const listeners = [];

    //传入enhancer处理的逻辑
    if(enhancer && typeof enhancer === 'function') {
        const newCreateStore = enhancer(createStore);
        const newStore = newCreateStore(reducer);
        return newStore;
    }

    function getState() {
        return state;
    }

    function dispatch(action) {
        state = reducer(state, action);
        listeners.forEach((listener) => {
            listener();
        })
    }

    function subscribe(listener) {
        listeners.push(listener);
    }

    return {
        getState,
        dispatch,
        subscribe
    }
}

我们回头看一下logger1,logger2这两个中间件的定义,这里面都会调用一个next方法,这个next调用的时候又会传入一个action,是不是特别熟悉 其实这个next就是dispatch方法,所以newCreateStore里面返回的dispatch可以理解成是用中间件加强以后的dispatch

那么如何用中间件加强dispatch呢? 举个简单的例子

function wrap1(dispatch) {

    function newDispatch(action) {
        console.log('wrap1-before');
        dispatch(action);
        console.log('wrap1-after');
    };

    return newDispatch;
}

function wrap2(dispatch) {
    function newDispatch(action) {
        console.log('wrap2-before');
        dispatch(action);
        console.log('wrap2-after');
    }
    return newDispatch;
}


function dispatch(action) {
    console.log('dispatch', action)
}


const newDispatch2 = wrap2(dispatch);
const newDispatch1 = wrap1(newDispatch2);
//简写
wrap1(wrap2(dispatch))

这个例子就通过wrap1和wrap2对dispatch进行了加强,所以要加强dispatch其实只要通过logger1(logger2(loggger...(dispath))),这个就是装饰器模式的典型应用 这里要介绍一个方法compose, 专门用来实现这种嵌套调用,这个方法在函数式编程中十分常用,compose方法调用方式如下 compose(logger1, logger2,...)(disaptch) 这样就返回了logger1,logger2加强以后的disaptch compose的实现如下

function compose() {
    const args = Array.from(arguments);
    return (x) => {
        //第一次执行prevFunc是传入的x,后面prevFunc就是每次的回调的执行结果
        return args.reduceRight((prevFunc, nextFunc) => {
            return nextFunc(prevFunc)
        }, x)
    }
}

这个方法的核心就是reduceRight这个方法,reduceRight这个方法的特点就是他会把前一次回调的结果当作参数传给下一次回调, 比如一个很常见的应用就是用和找个方法累加求和

const sum = [0, 1, 2, 3];
reduceRight((a, b) => a + b, 100);
// sum 的值是 106

这里a参数第一次执行的时候值是100, 第二个执行就变成上一次回调的结果(100 + 0 = 100) 更详细的介绍直接看文档吧 developer.mozilla.org/zh-CN/docs/…

有了compose方法,接下来实现applyMiddleware就很简单了

function applyMiddleware(...middlewares) {
    //返回一个enhancer
    function enhancer(createStore) {
        //返回一个新的createStore
        function newCreateStore(reducer) {
            const store = createStore(reducer);
            const {dispatch} = store;
            //遍历middlewares,把store传入执行middleware, 拿到所有的dispatch enhancer
            const dispatchEnhancers = middlewares.map((func) => {
                return func(store);
            })
            //通过compose方法得到加强后的dispath
            const newDispatch = compose(...dispatchEnhancers)(dispatch);
            return {...store, dispatch: newDispatch};
        }
        return newCreateStore;
    }

    return enhancer;
}

function compose() {
    const args = Array.from(arguments);
    return (x) => {
        //第一次执行prevFunc是传入的x,后面prevFunc就是每次的回调的执行结果
        return args.reduceRight((prevFunc, nextFunc) => {
            return nextFunc(prevFunc)
        }, x)
    }
}

applyMiddleware会返回一个enhancer, 调用enhancere拿到一个新的createStore方法, 新的createStore会返回一个disaptch加强后的store对象。

我们测试一下输出如下

applemiddleware.png

bindActionCreator

最后再来看一下bindActionCreator,一样先写个demo

const initState = {
    post: 1
}

function reducer(state=initState, action) {
    switch(action.type) {
        case 'INCREASE_POST':
            return {...state, post: state.post + action.count};
        case 'DECREASE_POST':
            return {...state, post: state.post - action.count};
        default:
            return state;
    }
}

let store = createStore(reducer);

store.subscribe(() => {
    console.log(store.getState());
})

const actions = {
    increasePost: () => {
          return {
            type: 'INCREASE_POST', 
            count: 1
        }
    },
    decreasePost:  () => {
        return {
            type: 'DECREASE_POST', 
            count: 1
        }
    }

}
const newActions = bindActionCreators(actions, store.dispatch);
newActions.increasePost();
newActions.decreasePost();

可以看到bindActionCreators返回的action,不需要再dispatch,在action执行的时候会帮我们执行dispatch操作,这个可以理解成加强了action 我们直接看代码实现把 逻辑应该挺简单的

function bindActionCreator(actionCreator, dispatch) {
    return function() {
        //这里一定要用apple或者call来调用 因为我们的newAction是开发者来调用的
        //开发者不知道里面会调用真实的action,开发者希望我调用newAction的时候的
        //上下文就是调用action的上下文 如果用call来实现 就要用action.call(this, ...arguments)
        return dispatch(actionCreator.apply(this, arguments));
    }
}

function bindActionCreators(actionCreators, dispatch) {
    if(typeof action === 'function') {
        return bindActionCreator(actionCreators, dispatch);
    }
    let newActionAcreator = {};
    for(const key in actionCreators) {
        newActionAcreator[key] = bindActionCreator(actionCreators[key], dispatch);
    }
    return newActionAcreator;
}

bindActionCreators支持传入action的map或者执行传入一个action function 如果传入的是一个function,直接调用bindActionCreator方法,这个方法会返回一个新的action,这个action调用的时候会直接dispath 如果传入的是一个map,逻辑其实是一样的,只是多了遍历这一步。

我们测试一下输出如下

bindactioncreator.png

到这里我们已经实现了自己的redux,下一篇文章我们实现一个react-redux。