美文网首页纵横研究院React技术专题社区
函数式编程在 Redux 源码中的应用

函数式编程在 Redux 源码中的应用

作者: Axtlive | 来源:发表于2019-11-05 14:25 被阅读0次

    前言

    Redux 正是函数式编程的一个经典实现,本文将依据原版的 redux ,简单讲述一下 redux 的函数式编程并完成自己的一个 redux ,让大家知其然知其所以然

    在这之前,你需要了解的东西:

    1. 纯函数,固定的输入就有固定的输出
    2. react 的函数组件体现就是纯函数,接受 props,输出的 view 就是纯的
    3. redux 本身就是一个容器的概念(container)
    4. react 内部有 state,就是容器(container)的值
    5. action 接受变形关系
    6. reducer 可以遍历(map)当前容器的值并执行相应修改更新操作
    7. middleware(中间件)也就是IO函子,它解决了异步的操作

    redux 的目录结构

    1. createStore.js 创建一个 redux 的 store ,用来存放 state
    2. bindActionCreators.js 把 action Creators 转化成拥有同名 keys 的对象,在组件内使用的时候直接的调用
    3. applyMiddleware.js 使用自定义的中间件来扩展 redux
    4. combineReducers.js 对reducer 进行拆分,拆分后每一块独立负责管理 state 的部分
    5. compose.js 从右向左组合函数,避免柯里化函数而写出洋葱式的代码 h(g(f(x)))
    6. index.js 向外输出的

    开始我们的 redux

    • 首先创建一个 index.js 文件,用于向外输出我们的核心(createStore)和其他方法
    • 就是一个对象从同级目录下导入,并导出
        import createStore from "./createStore.js";
        export { createStore, combineReducers };
    

    第一版 (createStore)

    • 创建核心文件(createStore.js)

      • 先在在内部实现 subscribe、changeState、getState 三个方法
      • 订阅(subscribe),常用的开发模式就是往里面放一个数据并把东西都塞到数组里
      let listeners = [];
          function subscribe(listener) {
              listeners.push(listener);
      }
      
      • 通知(changeState),对 listeners 里面的每一项执行派发执行
      function changeState(newState) {
          state = newState;
          for (let item of listeners) {
              item();
          }
      }
      
      • getState 就返回 state 的值
      function getState() {
          return state;
      }
      
      • 如此我们就完成了 createStore 的创建
    export default function createStore(initState) {
      let state = initState;
      let listeners = [];
      function subscribe(listener) {
        listeners.push(listener);
      }
    
     function changeState(newState) {
      state = newState;
       for (let item of listeners) {
        item();
      }
     }
    
      function getState() {
        return state;
      }
    
      return {
        subscribe,getState,changeState
      };
    }
    
    

    第一版总结

    这个版本对状态的管理没有约束,changeState是我们改变状态的唯一方法,此时我们需要使用到 reducer,从而有了我们的第二版

    第二版 (dispatch)

    • 第一版中的 changeState 方法是我们自己编出来的

    • 在 redux 中执行更新的是 reducer(业务逻辑),派发任务的方法是 dispatch

    • 如此我们把刚才的 changeState 改成 dispatch

    • 此时的 dispatch 不是随意执行了,而是按照 reducer 的规则来执行

    • createStore.js

    export default function createStore(reducer, initState) {
      let state = initState;
      let listeners = [];
      function subscribe(listener) {
        listeners.push(listener);
      }
    
      function dispatch(action) {
        state = reducer(state, action);     
        for (let item of listeners) {
          item();
        }
      }
    
      function getState() {
        return state;
      }
    
      return {
        subscribe,
        getState,
        dispatch
      };
    }
    
    • 业务逻辑(对状态的修改操作)其实是在reducer里面的,reducer 最终会返回一个新的 state ,在使用的时候,我们需要引入 reducer, 执行 createStore 并传入 reducer 和 initState,然后使用 dispatch 方法来派发当前的任务,表明任务的类型,然后使用 subscribe 来订阅可以获取更新后的值
    • 注意

    如果在使用 createStore 过程中只传入了 reducer 并没有传入 initState ,如此就会得到一个undefined,那么我们需要进行一步操作:在 createStore.js 文件里,自身调用一下 dispatch,传入的参数为 {type:Symbol()},有用户就要说了,那我传入一个 {type:'xxxxx'}不也行吗,那假如有个开发者在 reducer 里面写了一个 type 正好就是 'xxxxx' 不就熄火了吗,所以使用的 type 的值为 Symbol 是最可靠的,可以保证不会出现重复!

    第二版总结

    这个版本我们加入了 reducer (业务逻辑)来修改状态使用的值,但是有个确定,非计划外的任务不能修改,也就是当你传递的 action 不在 reducer 的类型中的时候,是不会进行更改的

    第三版 (combineReducer)

    • 当项目过于复杂时,就会出现多个 reducer,用来分别执行各自的修改

    • 此时就需要合并多个 reducer 成一个reducer 并使用

    • 此时我们创建一个 combineReducer.js 文件来完成多个 reducer 的合并

      • combineReducer 方法接受一个 reducers 的对象作为参数
      • 将 reducers 对象的键名(key)存为一个数组
         const reducerKeys = Object.keys(reducers);
      
      • 然后返回合并后的 reducer,让它成为一个大的 reducer,所以它和那些小的 reducer 长得都是一样的(一个function接受 state 和 action)
      return function combination(state = {}, action) {}
      
      • 遍历这个 reducerKeys 得到每个 reducer
      • 通过 key 来取到之前的状态(previousStateOfKey)
      • 则执行 reducer 传入之前的状态和 action 来得到下一个状态
      • 然后返回一个 nextState
      • 如此我们就完成了 combineReducer.js
      export default function combineReducers(reducers) {
      
      const reducerKeys = Object.keys(reducers);
      
      return function combination(state = {}, action) {
               const nextState = {};
               for (let key of reducerKeys) {
                   const reducer = reducers[key];
                   const previousStateOfKey = state[key];
                   const nextStateOfKey = reducer(previousStateOfKey, action);
                   nextState[key] = nextStateOfKey;
               }
               return nextState;
           };
       }
      
      
      • 使用 combineReducer
      import { combineReducer } from "./axtlive-redux/index.js";
       import counterReducer from "./reducers/counter.js";
       import infoReducer from "./reducers/info.js";
      
       const reducer = combineReducer({
           counter: counterReducer,
           info: infoReducer
       });
      

    第三版总结

    这个版本我们创建了 combineReducer 来合并多个小的 reducer 成为一个大的 reducer,使得多种逻辑处理相分离

    第四版 (middleware && applyMiddleware)

    • 众所周知,在 redux 里面 dispatch 一个 action ,它就会到达 reducer 进行处理,而middleware 就可以让我们在 dispatch action 之后,到达reducer之前,这个时间段内搞点事情,例如(打印、报错、跟异步API通信等等)
    • 有个需求,在每次 dispatch 了 action 之后,我们想要获取到下一个 state 的 值 (nextState)
        let action = doSomthing('123');
        console.log('dispatching',action)
        store.dispatch(action);
        console.log('nextState',store.getState())
    
    • 如上我们就可以实现这个需求了,接下来我们把它放到一个函数里面
        function getDispatchLog(store,action){
            let action = doSomthing('123');
            console.log('dispatching',action)
            store.dispatch(action);
            console.log('nextState',store.getState())
        }
    
    • 然而我们并不希望每次都来 import 这个函数,所以我们直接替代
        function logger(){
            let next = store.dispatch
            return function dispatchLog(action){
                let action = doSomthing('123');
                console.log('dispatching',action)
                next(action);
                console.log('nextState',store.getState())
            }
        }
    
    • 这样在下次使用 dispatch 的时候就可以获取到 nextState 了,好似一个 log 的功能,这样的功能我们就称之为 middleware ,用来增强 dispatch 的功能
    • 我们可以直接 return 这个函数,就可以在后面实现一个链式调用,赋值这件事就在 applyMiddleware里做
    • 接下来我们实现一个 applyMiddleware 来连接两个 middleware
        function myApplyMiddleware(store,middlewares){
            middlewares = middlewares.splice()
            middlewares.reverse()
            middlewares.forEach(middleware => {
                store.dispatch = middleware(store)
            })
        }
    
    • 在 applyMiddle 里必须要给 store.dispatch 赋值,否则下一个 middleware 就拿不到最新的dispatch,但是可以在 middleware 里不直接从 store.dipatch 里读取 next 函数,而是将 next 作为一个参数传入,在 applyMiddleware 里用的时候把这个参数传下去
    function logger(store) {
      return function wrapDispatchLog(next) {
        return function dispatchLog(action) {
          console.log('dispatching', action)
          let result = next(action)
          console.log('next state', store.getState())
          return result
        }
      }
    }
    
    • 对上述代码用 ES6 的柯里化写法,写成如下形式
        const logger = store => next => action => {
            console.log('dispatching', action)
            let result = next(action)
            console.log('next state', store.getState())
            return result
        }
    
    • 对比一下 thunk 这个中间件,它是用于异步的,由上可以看出 middleware 需要三个参数,store,next,action,而 thunk 就看第三个参数是否为一个 function,如果是则执行它,如果不是,则就按照原来的方式执行 next(action)

    • 然后我们再了解一下函数的合成,一个数据要经过多个函数的处理,才可以成为另外一个数据,那我们把所有中间的处理合并成一个函数的过程叫做函数的合成(compose)

    • compose 做的事情就是将上一个函数的返回结果作为下一个函数的参数传入

        export const compose = (...funcs) {
          if (funcs.length === 0) {
            return arg => arg
          }
          if (funcs.length === 1) {
            return funcs[0]
          }
      return funcs.reduce((a, b) => (...args) => a(b(...args)))
    }
    
    • 在redux源码里 createStore 的时候,如果发现第二个参数为一个 function 且第三个参数为 undefined,那么它会认为你使用了中间件则创建 store 完全交给了中间件
    • 下面我们就可以看到最终的 applyMiddleWare 了
        export const applyMiddleWare = (...middlewares) => (createStore) => (reducer,initState) => {
            const store = createStore(reducer,initState)
            let dispatch = store.dispatch
            const middleWareAPIs = middlewares.map(item => item({
                getState: store.getState,
                dispatch: action => dispatch(action)
            }))
            dispatch = compose(...middleWareAPIs)(dispatch)
            return {
                ...store,
                dispatch
            }
        }
    

    第四版总结

    这个版本我们实现了 applyMiddleWare 来增强 dispatch,其实在使用了 middleware 以后,创建 store 的任务就交给了 中间件,它最终也是会返回一个新的 store,subscribe 和 getState 方法并没有改变,只是拥有了一个新的 dispatch 方法

    总结

    1. 本文就 createStore、combinReducers、compose、applyMiddleWare 的思想对创建步骤进行了讲述
    2. 纯函数,有固定的输入,就有固定的输出
    3. createStore 作为核心部分,内部拥有subscribe、dispatch、getState 三个方法,用于订阅、通知、获取
    4. combinReducers 会对多个小的 reducer 进行合并,然后返回一个大的 reducer 来进行业务逻辑的处理
    5. compose 就是将上一个函数的执行结果作为下一个函数的参数传入
    6. applyMiddleWare 则是使用一些中间件(在 dispatch action 之后,到达 reducer 之前执行的一些操作的方式)来增强 dispatch(比如执行一下异步的操作等)

    相关文章

      网友评论

        本文标题:函数式编程在 Redux 源码中的应用

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