手写 Redux 及其类库

242 阅读3分钟

为什么是 Redux

我们知道 Redux 是一款状态管理类库,首先要明确为什么需要它?

对于跨层级的组件通信,React 中开始是使用 Context。但 Context 属于简单状态管理类库。显著特征是把要传递的数据放在组件最顶层,然后往下传递。对大量复杂的状态数据,传递下去的数据状态难以做到准确的管理,有时会造成重复渲染,在 React 中属于高级 API ,需要谨慎使用。

所以引出 Redux,单独的状态管理库,强调单向数据流,更改状态的方法提前声明,且只能通过 dispatch 触发。和 React 组件本身做解耦。

image.png

Redux 使用五步曲

  • createStore  创建 store ,入参为 reducer
  • reducer  初始化、定义好修改要状态的函数
  • store.getState()  获取状态
  • store.dispatch({type: '', payload:})  派发更新
  • store.subscribe  订阅状态变更

实现 Redux 及其类库

关键节点按照注释中的顺序阅读,应该不会迷路。

/**
 * 第二个参数就是 applyMiddleware 的执行结果
 * 是一些中间件,核心用来加强 dispatch,比如支持 异步
 * @param {*} reducer
 * @param {*} enhancer
 * @returns store
 */
function createStore(reducer, enhancer) {
    let curState;
    let subscribeList = [];
    //2. 传递进来的 enhancer 是一个函数,为啥呢,因为我们要对原来的 dispatch 做加强
    //   首先要得到原来的 dispatch,需要先传递 createStore
    //3. 传递完 createStore 之后,函数内部会注入中间件,最后传入 reducer 生成包含加强
    //   后的 dispatch。这里有相当于是一个高阶函数,需要仔细看。
    if(enhancer) {
        enhancer(createStore)(reducer)
    }
    function getState() {
        return curState;
    }
    function dispatch({type, payload}) {
        curState = reducer({type, payload});
        subscribeList.map(listener => listener());
    }
    function subscribe(listener) {
        subscribeList.push(listener)
    }
    // 第一次初始化 state 的值
    dispatch({type: '@init'});
    return {
        getState,
        dispatch,
        subscribe,
    }
}

/**
 * @param {*} middlewares
 * @returns store
 * 使用方式
 * 0. let store = createStore(golbalReducer, applyMiddleware(logger, thunk));
 * 
 * 1. 传给 createStore 的是 applyMiddleware 的执行结果
 */
function applyMiddleware(...middlewares){
    
    return (createStore) => (reducer) => {
        // 4. 传入 reducer  生成新的 store
        const store  = createStore(reducer);
        // 5. 处理中间件
        const injectPrams = {
            getState: store.getState,
            dispatch: store.dispatch
        }

        // 得到的 middlewaresFn 是传入了 middlewaresFn 的回调函数:
        // action => {
        //     console.log('some logger');
        //     return dispatch(action)
        // }
        const middlewaresFn = middlewares.map(fn => fn(injectPrams));

        // 使用 compose 将 middlewaresFn 聚合起来,传入 dispatch 得到新的 dispatch
        // 类似 const dispatch = logger(thunk(dispatch));
        // ! 把真正的dispatch包在最里面 !这里挺绕的要仔细看 !!!
        // 第一次执行 thunk 返回一个回调函数
        // 第二次执行 logger 也返回一个回调函数,并把第一次返回的回调函数当作参数传入。
        // 当真正执行第二次回调的时候,会把第一次传进来的参数在末尾执行
        // 当外部调用最后经过包装的dispatch 时,才真正开始执行回调函数 
        const dispatch = compose(middlewaresFn)(store.dispatch);

        return {
            ...store,
            dispatch,
        }
    }
}
/**
 * 0 处传入的logger中间件长这样
 */
function logger(dispatch) {
    return action => {
        console.log('some logger');
        return dispatch(action)
    }
}
/**
 * 0 出传入的thunk中间件长这样
 */
function thunk(dispatch) {
    return action => {
        if(typeof action === 'function') {
            return action(dispatch);
        }else{
            return dispatch(action);
        }
    }
}

function compose(...fns) {
    return fns.reduce((prev, next)=>(...args)=> {prev(next(...args))});
}

export {
    createStore,
    applyMiddleware
}

redux、react-redux、redux-saga、redux-thunk 之间的区别联系

应该很清晰了,redux-saga、redux-thunk 是 redux 的中间件,用来加强原本的 dispatch。react-redux 是专门适配 react 的,核心是加了层中间函数,方便书写和管理,后续再写 O_o。

image.png

「 一枚前端学习小透明,努力学习前端知识,同时分享自己对生活的一些思考,欢迎一起讨论交流。如果我的文章对你有帮助,请点个赞,会非常感恩你的鼓励。完」