Redux 进阶:中间件的使用

什么是 middleware

用过 Express 或 Koa 相似框架的同窗可能知道,在 Express 中,中间件(middleware)就是在 req 进来以后,在咱们真正对 req 进行处理以前,咱们先对 req 进行必定的预处理,而这个预处理的过程就由 middleware 来完成。

同理,在 Redux 中,middleware 就是扩展了在 dispatch action 以后,到 action 到达 reducer 以前之间的中间这段时间,而中间的这段时间就是 dispatch 的过程,因此 Redux 的 middleware 的原理就是改造 dispatchjavascript

自定义 middleware

让咱们先从一个最简单的日志 middleware 定义开始:html

const logger = store => next => action => {
  console.group('logger');
  console.warn('dispatching', action);

  let result = next(action);

  console.warn('next state', store.getState());
  console.groupEnd();

  return result;
};

这个 logger 函数就是一个 Redux 中的 middleware ,它的功能是在 store.dispatch(action)(对应 middleware 中的 next(action)) 以前和以后分别打印出一条日志。从咱们的 logger 中能够看到,咱们向 middleware 中传入了 store,以便咱们在 middleware 中获取使用 store.getState() 获取 state,咱们还在以后的函数中传入了 next,而最后传入的 action 就是咱们平时 store.dispatch(action) 中的 action,因此 next(action) 对应的就是 dispatch(action)java

最后咱们还须要调用并 next(action) 来执行本来的 dispatch(action)git

使用 middleware

最后咱们能够在使用 createStore() 建立 store 的时候,把这个 middleware 加入进去,使得每次 store.dispathc(action) 的时候都会打印出日志:github

import { createStore, applyMiddleware } from 'redux';  // 导入 applyMiddleware

const store = createStore(counter, applyMiddleware(logger));

注意,这里咱们使用了 Redux 提供的 applyMiddleware() 来在建立 store 的时候应用 middleware,而 applyMiddleware() 返回的是一个应用了 middleware 的 store enhancer,也就是一个加强型的 store。编程

createStore() 接受三个参数,第一个是 reducer,第二个若是是对象,那么就被做为 store 的初始状态,第三个就是 store enhancer,若是第二个参数是函数,那么就被看成 store enhancer。json

关于 applyMiddleware 和咱们自定义的 logger 是如何一块儿工做的,这个咱们稍后再讲。redux

为了说明后一条日志 console.warn('next state', store.getState()) 是在执行了 reducer 以后打印出来的,咱们在 reducer 中也打印一个消息。改造后的 reducer:api

function counter(state = 0, action) {
+  console.log('hi,这条 log 从 reducer 中来');
    switch(action.type) {
      case 'INCREMENT':
        return state + 1;
      case 'DECREMENT':
        return state - 1;
      default :
        return state;
    }
 }

结果

logger

这里,我使用了 #1 中的计数器做为例子。服务器

能够看到,在 reducer 中打印的消息处于 middleware 日志的中间,这是由于在 logger middleware 中,将 let result = next(action); 写在了最后一条消息的前面,一旦调用了 next(action),就会进入 reducer 或者进入下一个 middleware(若是有的话)。相似 Koa 中间件的洋葱模型。

其实 next(action) 就至关于 store.dispatch(action),意思是开始处理下一个 middleware,若是没有 middleware 了就使用原始 Redux 的 store.dispatch(action) 来分发动做。这个是由 Redux 的 applyMiddleware 来处理的,那么 applyMiddleware() 是如何实现对 middleware 的处理的呢?稍后咱们会对它进行简单的讲解 。

❓applyMiddleware 是如何实现的

applyMiddleware 的设计思路 中,咱们能够看到 Redux 中的 store 只是包含一些方法(dispatch()subscribe()getState()replaceReducer())的对象。咱们可使用

const next = store.dispatch;

来先引用原始 store 中的 dispatch 方法,而后等到合适的时机,咱们再调用它,实现对 dispatch 方法的改造。

Middleware 接收一个名为 next 的 dispatch 函数(只是 dispatch 函数的引用),并返回一个改造后的 dispatch 函数,而返回的 dispatch 函数又会被做为下一个 middleware 的 next,以此类推。因此,一个 middleware 看起来就会相似这样:
function logger(next) {
  return action => {
    console.log('在这里中一些额外的工做')
    return next(action)
  }
}

其中,在 middleware 中返回的 dispatch 函数接受一个 action 做为参数(和普通的 dispatch 函数同样),最后再调用 next 函数并返回,以便下一个 middleware 继续,若是没有 middleware 则 直接返回。

因为 store 中相似 getState() 的方法依旧很是有用,咱们将 store 做为顶层的参数,使得它能够在全部 middleware 中被使用。这样的话,一个 middleware 的 API 最终看起来就变成这样:
function logger(store) {
  return next => {
    return action => {
      console.log('dispatching', action)
      let result = next(action)
      console.log('next state', store.getState())
      return result
    }
  }
}

值得一提的是,Redux 中使用到了许多函数式编程的思想,若是你对

  • curring
  • compose
  • ...

比较陌生的话,建议你先去补充如下函数式编程思想的内容。applyMiddleware 的源码

❓middleware 有什么应用的场景

  • 打印日志,好比上面咱们自定义的 middleware;
  • 异步 action,好比用户对服务器发起请求,在等待返回响应的时间里,咱们能够更新 UI 为 Loading,等到响应返回时,咱们再调用 store.dispatch(action) 来更新新的 UI;
  • ...

一个使用异步 action 请求 Github API 的例子

经过仿照 redux-thunk,咱们也能够本身写一个支持异步 action 的 middleware,以下:

const myThunkMiddleware = store => next => action => {
  if (typeof action === 'function') {    // 若是 action 是函数,通常的 action 为纯对象
    return action(store.dispatch, store.getState);    // 调用 action 函数
  }
  return next(action);
};

异步 action creator :

export function fetchGithubUser(username = 'bbbbx') {
  return dispatch => {
    // 先 dispatch 一个同步 action
    dispatch({
      type: 'INCREMENT',
      text: '加载中...'
    });

    // 异步 fetch Github API
    fetch(`https://api.github.com/search/users?q=${username}`)
      .then(response => response.json())
      .then(responseJSON => {
        // 异步请求返回后,再 dispatch 一个 action
        dispatch({
          type: 'INCREMENT',
          text: responseJSON
        });
      });
    };
}

修改 reducer,使它能够处理 action 中的 action.text

function counter(state = { value: 0, text: '' }, action) {
  switch(action.type) {
    case 'INCREMENT':
      return {
        value: state.value + 1,
        text: action.text
      };
    case 'DECREMENT':
      return {
        value: state.value - 1,
        text: action.text
      };
  default :
    return state;
  }
}

再改造一下 Counter 组件,展现 Github 用户:

// Counter.js
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      username: ''
    };
  }

  handleChange(event) {
    this.setState({
    username: event.target.value
    });
  }

  handleSearch(event) {
    event.preventDefault();
    if (this.state.username === '') {
      return ;
    }
    this.props.fetchGithubUser(this.state.username);
  }

  render() {
    const { text, value, increment, decrement } = this.props;
    let users = text;
    if (text.items instanceof Array) {
      if (text.items.length === 0) {
        users = '用户不存在!';
      } else {
        users = text.items.map(item => (
          <li key={item.id}>
          <p>用户名:<a href={item.html_url}>{item.login}</a></p>
          <img width={100} src={item.avatar_url} alt='item.avatar_url' />
          </li>
        ));
      }
    }

    return (
      <div>
        Click: {value} times {' '}
        <button onClick={increment} >+</button>{' '}
        <button onClick={decrement} >-</button>{' '}
        <div>
          <input type='text' onChange={this.handleChange.bind(this)} />
          <button onClick={this.handleSearch.bind(this)} >获取 Github 用户</button>{' '}
        </div>
        <br />
        <b>state.text:{users}</b>
      </div>
    );
  }
}

结果

redux-thunk-example

使用已有的 Redux 中间件

redux-thunk

利用 redux-thunk ,咱们能够完成各类复杂的异步 action,尽管 redux-thunk 这个 middleware 只有 数十行 代码。先导入 redux-thunk:

import thunkMiddleware from 'redux-thunk';

const store = createStore(
  counter,
  applyMiddleware(thunkMiddleware)
);

以后即可定义异步的 action creator 了:

export function incrementAsync(delay = 1000) {
  return dispatch => {
    dispatch(decrement());
    setTimeout(() => {
      dispatch(increment());
    }, delay);
  };
}

使用:

<button onClick={increment} >+</button>{' '}
   <button onClick={decrement} >-</button>{' '}
+ <button onClick={() => incrementAsync(1000) } >先 - 1 ,后再 + 1</button>{' '}

注意,异步 action creator 要写成 onClick={() => incrementAsync(1000) } 匿名函数调用的形式。

结果

incrementasync

相关文章
相关标签/搜索