xiaochengzi6 / Blog

个人博客
GNU Lesser General Public License v2.1
0 stars 0 forks source link

Redux 中间件与异步操作 #32

Open xiaochengzi6 opened 2 years ago

xiaochengzi6 commented 2 years ago

一、中间件(midleware)是做什么的?

Redux 不能自动处理 store.dispatch 的返回的函数(异步函数),但在 dispatch和 reducer 之间增加一个中间件就可以将这个函数转换成适合redux处理的内容。

action ---> dispatch ---> middleware 1 ---> middleware 2 ---> reducers

中间件就是一个函数对 store.dispatch() 方法的重新包装,也就是在 actionreducer 这两步之间添加了其他功能生成一种洋葱模型

img

store.dispatch会被层层包装成一个洋葱模型,这样就可以在里面执行一些副作用和异步操作。

二、为什么中间件去改造 dispatch 而不是在其他的位置?

1) 先来看一下Redux的一个同步的例子。

//reducer
const Redux = (state, action) => state
//store
const store = createStore(Redux)
//action
{ type: 'NAME', value: 'value' }
//store.dispatch
store.dispatch({ type: 'NAME', value: 'value' })

由于:

Reducer是纯函数只承担对 State 的计算。

view 和state 一一对应也不能作为承担其他的功能。

action 存放数据的对象,只能被别人操作,自己不能执行任何操作。

所以: 就选定在store.dispatc() 方法中添加功能既可以在数据 action 发出时做出相应的动作也可以更容易的操作 action 。

那就先做一个简易的中间件看看

let next = store.dispatch;
store.dispatch = function dispatch_Log(action) {
    console.log('action:', action);
    next(action);
    console.log('next state:', store.getState())
}

可以看到将原来的方法进行保留然后重新对 dispatch 重新定义在调用 next(action) 时候开始打印功能。让我们在回顾一下对中间件的定义:中间件就是一个函数对 store.dispatch() 方法的重新包装,也就是在 actionreducer 这两步之间添加了其他功能生成一种洋葱模型

三、使用示例

import { createStore, applyMiddleware } from 'redux';
//state
const state = {
    score: 2
}
//Reducer
const reducer = (state, action) => {
    switch(action.type) {
            case: 'SCORE_VALUE'
            return { ...state, score: action.score}
    }
}
//中间件 (看不懂没关系往下看)
cosnt logger = ({ getState, dispatch } => next => action =>{
    console.log('log开始:',action)
    let returnValue =  next(action)
    console.log('log结束:', action)
    return returnValue
})
//store
let store = createStore(reducer, state, applyMiddleware(logger))
//store.dispatch
store.dispatch({
    type: 'SCORE_VALUE',
    score: 5
})

首先先看 createStore 这个函数接收三个参数 createStore(reducer, [preloadedState], enhancer)

  1. reducer (Function): 接收两个参数,分别是当前的 state 树和要处理的 [action](https://www.redux.org.cn/docs/Glossary.html#action),返回新的 [state 树](https://www.redux.org.cn/docs/Glossary.html#state)
  2. [preloadedState] (any): 初始时的 state。
  3. enhancer (Function): enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator。(请牢牢记住这一点)

为了深入了解 enhancer 来先看以下 它接收那些参数 。由于 enhancer 是返回一个新的强化后的 store creator 可以先看到 StoreCreator 它的函数签名:

StoreCreator 的函数签名为

type StoreCreator = (reducer: Reducer, State: ?State) => Store

enhancer 的签名为

type enhancer = (next: StoreCreator) => StoreCreator

现在就明白了 enhancer 作为一个类似于 react 高阶组件的去接收 StoreCreator 然后在返回一个加强版的 StoreCreator 需要注意:enhancer 就是指代的 applyMiddleware(...midleware)

//Reducer
const reducer = (state, action) => {
    switch(action.type) {
            case: 'SCORE_VALUE'
            return { ...state, score: action.score}
    }
}
//store
let store = createStore(reducer, applyMiddleware(thunk))

注明:高阶组件是 react 的一种设计模式 它是一个函数接收一个组件为参数然会在返回新的组件

为了直观的感受 enhancer 的魅力 所以...

export default function applyMiddleware(...middlewares) {
    // 接收 createStore 参数
  return (createStore) => (reducer, preloadedState, enhancer) => {
    // 保存 store disptch chain
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    // 传递给中间件的参数
    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }

    // 注册中间件调用链,并由此可知,所有的中间件最外层函数接收的参数都是{getState,dispatch}
        // 这里就可以明确为什么编写中间件(logger)需要接收一个{ getState, dispatch } 
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    //compose 函数起到代码组合的作用:compose(f, g, h)(...args) 效果等同于 f(g(h(...args)))
    //从此也可见:所有的中间件最二层函数接收的参数为 dispatch,一般我们在定义中间件时这个形参不叫dispatch 而叫 next,
    //是由于此时的 dispatch 不一定是原始 store.dispatch,有可能是被包装过的新的 dispatch。
    dispatch = compose(...chain)(store.dispatch)
    //被包装后的dispatch 也被称为 next 
    // 返回经 middlewares 增强后的 createStore
    return {
      ...store,
      dispatch
    }
  }
}
//中间件 (现在看懂了吗?)
cosnt logger = ({ getState, dispatch } => next => action =>{
    console.log('log开始:',action)
    let returnValue =  next(action)
    console.log('log结束:', action)
    return returnValue
})

这里就能感受到 action --中间件--> reducer 中间件的拦截作用

异步中间件 redux-thunk 简化版

const thunk = ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState);
      //如果传入的 action 是个函数那么他会将参数传入里面 
      //直到 action 不是参数的情况下开始执行 next(action)
      //这里没有完全明白 看看阮一峰博客
    }
    return next(action);
  };

function thunk () {
    return function ({dispatch, getState}) {
        return function (next) {
            return function (action) {
                if(typeof action === 'function'){
                    return action(dispatch, getState)
                }
                return next(action)
            }
        }
    }
}

compose 函数实现

// ...funcs === [f,g,h], store.dispatch
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg  //透传
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
  // composed 是上次计算的返回值 f 是当前元素 last(...args)是初值第一运行就会赋值给 composed
 // f(g(h(store.dispatch)))
}

array.reduceRight(function(total, currentValue, currentIndex, arr), initialValue) 作用:从数组的末尾向前将数组中的数组项执行 所定义的函数 操作

[解读 Redux 中间件的原理][https://juejin.cn/post/6844903502641102855]