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

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

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

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

js 复制代码
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 其实这就是一个发布订阅模式的实现,逻辑其实很简单

js 复制代码
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,输入如下

combineReducers

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

js 复制代码
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 我们的实现如下

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

我们测试一下输出如下

applyMiddleware

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

js 复制代码
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方法

js 复制代码
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呢? 举个简单的例子

js 复制代码
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的实现如下

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

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

js 复制代码
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就很简单了

js 复制代码
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对象。

我们测试一下输出如下

bindActionCreator

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

js 复制代码
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 我们直接看代码实现把 逻辑应该挺简单的

js 复制代码
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,逻辑其实是一样的,只是多了遍历这一步。

我们测试一下输出如下

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

相关推荐
y先森1 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy1 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189111 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿2 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡3 小时前
commitlint校验git提交信息
前端
虾球xz4 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇4 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒4 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员4 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐4 小时前
前端图像处理(一)
前端