美文网首页
Redux入门教程(二):异步 Action和中间件

Redux入门教程(二):异步 Action和中间件

作者: 变态的小水瓶 | 来源:发表于2020-05-29 00:37 被阅读0次

    一般action作为一个对象传入store,reducer直接计算好state结果即可。
    但是,有一种常用的情况就是向服务端获取数据,这种异步操作该怎么办?

    一、异步 Action

    首先,当调用异步 API 时,有两个非常关键的时刻:发起请求的时刻,和接收到响应的时刻。这两个时刻都可能会更改应用的 state,比如:开始请求时需要页面展示一个loading状态,收到数据后展示新数据。所以,我需要在不同时刻 dispatch 不同类型的 action,比如开始,成功,异常的action等等。

    如何把action和网络请求结合起来呢?
    站在框架作者的角度,看了看action,reducer,store三个模块,想来想去决定修改包装一下store中的store.dispatch()方法,就是要在action发出后,执行reducer之前动手脚,重新包装下dispatch,在这个过程中处理异步请求。

    image.png
    我们可能遇到如下这种情况,点击一个按钮,发送异步请求后再修改状态,也就是当action是个函数的时候:
    //  点击出发asyncAdd执行
    const asyncAdd = () => {
        store.dispatch((dispatch) => {
            setTimeout(() => {
                dispatch({ type: "add" })
            }, 1000);
        });
    }
    

    那么就需要引入中间件来帮我们解决这个问题。

    二、中间件

    接着上面,Action一般是个对象,当 Action为函数时,reducer就不好处理了,所以需要中间件处理这个返回的函数。
    比如,这个函数会被 Redux Thunk middleware 执行。此外,这个函数还可以 dispatch 一个action,就像 dispatch 前面定义的同步 action 一样,中间件redux-thunk究竟长什么样才能完成这个功能,又是如何使用的呢?

    使用中间件

    import { createStore, applyMiddleware } from "redux";
    import logger from "redux-logger";
    import thunk from "redux-thunk";
    
    function reducer(currentState = 0, action) {
        switch (action.type) {
            case 'add':
                return currentState + 1
                break;
            case 'minus':
                return currentState - 1
                break;
            default:
                return currentState
                break;
        }
    }
    // 使用redux提供的applyMiddleware将中间件进行聚合
    const store = createStore(reducer, applyMiddleware(thunk, logger));
    export default store;
    

    createStore:redux中暴露的一个方法,定义一个store对象,并定义了store的方法,如store.dispatch;
    reducer:根据项目需求自己写的纯函数;
    thunk:redux-thunk中间件函数;
    applyMiddleware:redux中暴露的一个方法,用于引入中间件,返回一个可以对createStore进行加强的函数,我们可以称之为enhancer;
    接下来的问题来了,applyMiddleware,thunk,createStore分别长什么样,是怎么一起配合的?

    createStore源码如下:

    import $$observable from 'symbol-observable'
    
    import ActionTypes from './utils/actionTypes'
    import isPlainObject from './utils/isPlainObject'
    
    export default function createStore(reducer, preloadedState, enhancer) {
      if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
        enhancer = preloadedState
        preloadedState = undefined
      }
      // enhancer应该为一个函数
      if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') {
          throw new Error('Expected the enhancer to be a function.')
        }
        //enhancer 接受 createStore 作为参数,对  createStore 的能力进行增强,并返回增强后的  createStore 。
        //  然后再将  reducer 和  preloadedState 作为参数传给增强后的  createStore ,最终得到生成的 store
        return enhancer(createStore)(reducer, preloadedState)
      }
      // reducer必须是函数
      if (typeof reducer !== 'function') {
        throw new Error('Expected the reducer to be a function.')
      }
    
     // 初始化参数
      let currentReducer = reducer   // 当前整个reducer
      let currentState = preloadedState   // 当前的state,也就是getState返回的值
      let currentListeners = []  // 当前的订阅store的监听器
      let nextListeners = currentListeners // 下一次的订阅
      let isDispatching = false // 是否处于 dispatch action 状态中, 默认为false
    
      // 这个函数用于确保currentListeners 和 nextListeners 是不同的引用
      function ensureCanMutateNextListeners() {
        if (nextListeners === currentListeners) {
          nextListeners = currentListeners.slice()
        }
      }
    
      // 返回state
      function getState() {
        if (isDispatching) {
          throw new Error(
            ......
          )
        }
        return currentState
      }
    
      // 添加订阅
      function subscribe(listener) {
      ......
        }
      }
    // 分发action
      function dispatch(action) {
        ......
      }
    
      //这个函数主要用于 reducer 的热替换,用的少
      function replaceReducer(nextReducer) {
        if (typeof nextReducer !== 'function') {
          throw new Error('Expected the nextReducer to be a function.')
        }
        // 替换reducer
        currentReducer = nextReducer
        // 重新进行初始化
        dispatch({ type: ActionTypes.REPLACE })
      }
    
      // 没有研究,暂且放着,它是不直接暴露给开发者的,提供了给其他一些像观察者模式库的交互操作。
      function observable() {
        ......
      }
    
      // 创建一个store时的默认state
      // 用于填充初始的状态树
      dispatch({ type: ActionTypes.INIT })
    
      return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
        [$$observable]: observable
      }
    }
    

    上面代码先不管其他的,就看这一句:
    return enhancer(createStore)(reducer, preloadedState);
    我们知道enhancer是applyMiddleware(thunk, promise, logger)返回的增强函数,这个函数可以处理createStore,enhancer(createStore)得到的是一个已经被加强了的createStore,这个加强了的createStore函数可以接收一些参数来创建store,包装dispatch,并返回store。

    applyMiddleware源码如下:

    export default function applyMiddleware(...middlewares) {
      return createStore => (...args) => {
        // 利用传入的createStore和reducer和创建一个store
        const store = createStore(...args)
        let dispatch = () => {
          throw new Error(
          )
        }
        const middlewareAPI = {
          getState: store.getState,
          dispatch: (...args) => dispatch(...args)
        }
        // 让每个 middleware 带着 middlewareAPI 这个参数分别执行一遍
        const chain = middlewares.map(middleware => middleware(middlewareAPI))
        // 接着 compose 将 chain 中的所有匿名函数,组装成一个新的函数,即新的 dispatch
        dispatch = compose(...chain)(store.dispatch)
        return {
          ...store,
          dispatch
        }
      }
    }
    

    这里又出现了一个compose,我们来看看它有什么用?

    compose 源码如下:

    compose 可以接受一组函数参数,将这一组函数从右到左一层层嵌套,然后返回一个组合函数。

    export default function 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)))
    }
    

    上面代码中...funcs对应了一组函数序列,是个解构赋值的写法,funcs代表一个函数数组,数组方法reduce进行数组遍历迭代,将数组前两项a,b组合成一个组合函数,使b函数执行完返回的值能作为a函数的参数,以此类推。
    例如:

    compose(funcA, funcB, funcC)
    

    得到的是:

    (...args) =>funcA(funcB(funcC(...args)))
    

    此时再与applyMiddleware源码联系在一起看,这个...args参数就是store.dispatch,其用意在于,先将其传给funcC就行改造,改造完成后将改造后的dispatch方法传给funcB,以此类推,目的在于可以后一个中间件可以接着处理前一个中间件改造后的dispatch

    中间件redux-thunk:

    function createThunkMiddleware(extraArgument) {
      return ({ dispatch, getState }) => next => action => {
        if (typeof action === 'function') {
          return action(dispatch, getState, extraArgument);
        }
        return next(action);
      };
    }
    const thunk = createThunkMiddleware();
    thunk.withExtraArgument = createThunkMiddleware;
    export default thunk;
    

    上面代码,next代表下一个中间件函数,action代表上面Action Creator返回的那个函数。
    redux-thunk 中间件的功能也很简单,createThunkMiddleware返回一个函数,这个函数检查参数 action 的类型,如果是函数的话,就执行这个 action 函数,并把 dispatch, getState, extraArgument 作为参数传递进去,否则就调用 next 让下一个中间件继续处理 action 。

    相关文章

      网友评论

          本文标题:Redux入门教程(二):异步 Action和中间件

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