zp1112 / blog

地址
http://issue.suzper.com/
36 stars 3 forks source link

很难理解的中间件原理以及redux-thunk中间件应用,溜溜的! #11

Open zp1112 opened 6 years ago

zp1112 commented 6 years ago

晦涩难懂

首先明白中间件的基本思想就是一个数据流的思想,就像一个restful接口,在请求到达之前先经过一层或者多层的预处理即拦截处理完最终再交给下面的执行器。我们就redux中间件的例子来说一下。

先盗一张图

image

redux中间件middleware是用于增强store.dispatch的,比如我们dispatch任何action的时候,都想把这个action打印出来,那么我们不需要在每个reducer里面或者修改dispatch来达到目的,我们只需要写一个中间件,中间件将这个action先处理完后,再交给下一个中间件处理,最终交给原生的dispatch处理。

那么如何写这个中间件的思路呢,首先,我们最终的目的地是dispatch(action),假设dispatch长这样

let dispatch = (action) => {
  console.log(333, action)
}

那么我们先写个中间函数是这样的格式

const cons = (next) => action => {
  console.log(111);
  next(action);
}

这里的next就是原生的dispatch函数, 我们重新定义dispatch = cons(dispatch),那么执行dispatch(action)的时候就会先执行打印,再执行原生的dispatch了。

假设我现在还想写一个中间件,

const cons2 = (next) => action => {
  console.log(222);
  next(action);
}

此时我们重新定义dispatch = cons2(cons(dispatch)),就会先执行cons2的打印在执行cons的打印,通俗的讲下来,完整代码是这样的:

// 类比原生dispatch
let dispatch = (action) => {
  console.log(333, action)
}
// 中间件1
const cons = (next) => action => {
  console.log(111);
  next(action);
}
// 中间件2
const cons2 = (next) => action => {
  console.log(222);
  next(action);
}
// 增强的dispatch
dispatch = cons2(cons(dispatch))
// 调用增强的dispatch
dispatch({key: 2})  // 222,111,333  数据action的流向是cons2->cons->dispatch

applyMiddleware原理

使用applyMiddleware的方式是

const store = createStore(reducers, applyMiddleware(thunk));

applyMiddleware源码核心部分

export default function applyMiddleware(...middlewares) {
  return (next) => (reducer, initialState) => {
    const store = next(reducer, initialState);
    const middlewareAPI = {
// 这里的getState也保证了每次的middlewares使用的store.state是最新状态的state
      getState: store.getState,
// 这里使用闭包使得每次执行middlewares的dispatch都是当前最新的,即middleware2拿到的dispatch是经过middleware1增强后的dispatch。
      dispatch: (action) => dispatch(action) 
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI));// 将最新的state和dispatch传入中间件,使得中间件可以获取最新的状态和dispatch。
    const dispatch = compose(...chain)(store.dispatch) // compose是redux提供的一个函数实现上述从f(),g(),h()到f(g(h(dispatch)))的过程。具体实现自行百度。。。懒得写了。
    return {
      ...store,
      dispatch // 最终将得到的增强的dispatch,覆盖了原生的store里面的dispatch。
    }
  }
}

以上分析,为了使得中间件可以获取最新的状态和dispatch,每个middleware有多包裹了一层闭包,从原来的

middleware = next => action => {}
// 变成了
middleware = ({dispatch, getState}) => next => action => {}
// 因此applyMiddleware中将其执行一次后再进行compose组合

redux-thunk原理

由于原生的dispatch只接受一个对象形式的action,不接受其他action,而且原生dispatch是同步模式,Action 发出以后,Reducer 立即算出 State。Action 发出以后,过一段时间再执行 Reducer,这就是异步。因此当我们需要在action里面执行一些异步操作的时候,就需要先处理这个异步的步骤,这时候中间件就派上用场了,redux-thunk中间件实现了这个功能。 使用方式

function fetchData() {
  return (dispatch, getState) => {
    setTimeout(() => {
      dispatch({type: 'test', text: 'haha'});
    }, 1000);
  }
} 

从这个使用方式可以很轻松的推测出redux-thunk的实现思路

const thunk = ({dispatch, getState}) =>(next) => action => {
  if (typeof action === 'function') { // 异步函数
    return action(dispatch, getState); // 注意此处的dispatch是最新的dispatch,进入循环
  }
  next(action);
}

当然thunk和的applyMiddleware源码更加复杂,但是核心就是这么些,弄懂了核心,boom!

其实感觉这个中间件描述起来晦涩难懂,好难描述,不过既然都提上日程了,不写也得咬着牙写了。。

WoooTer commented 6 years ago

溜溜溜溜溜溜

lrinili commented 6 years ago

文档写很留啊,难得写这么多

zp1112 commented 6 years ago

@lrinili 因为最近看那本书,收获比较多,推荐给你 《React进阶学习》