Redux 进阶:中间件的使用

栏目: 服务器 · 发布时间: 5年前

内容简介:用过 Express 或 Koa 类似框架的同学可能知道,在 Express 中,中间件(middleware)就是在 req 进来之后,在我们真正对 req 进行处理之前,我们先对 req 进行一定的预处理,而这个预处理的过程就由 middleware 来完成。同理,在 Redux 中,middleware 就是扩展了在 dispatch action 之后,到 action 到达 reducer 之前之间的中间这段时间,而中间的这段时间就是 dispatch 的过程,所以让我们先从一个最简单的日志 mi

什么是 middleware

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

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

自定义 middleware

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

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)

最后我们还需要调用并 next(action) 来执行原本的 dispatch(action)

使用 middleware

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

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。

关于 applyMiddleware 和我们自定义的 logger 是如何一起工作的,这个我们稍后再讲。

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

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;
    }
 }

结果

Redux 进阶:中间件的使用

这里,我使用了 #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 的处理的呢?稍后我们会对它进行简单的讲解 。

:question: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 的源码

:question:middleware 有什么应用的场景

store.dispatch(action)

一个使用异步 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 进阶:中间件的使用

使用已有的 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) } 匿名函数调用的形式。

结果

Redux 进阶:中间件的使用


以上所述就是小编给大家介绍的《Redux 进阶:中间件的使用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编程原本

编程原本

Alexander Stepanov、Paul McJones / 裘宗燕 / 机械工业出版社华章公司 / 2012-1-10 / 59.00元

本书提供了有关编程的一种与众不同的理解。其主旨是,实际的编程也应像其他科学和工程领域一样基于坚实的数学基础。本书展示了在实际编程语言(如C++)中实现的算法如何在最一般的数学背景中操作。例如,如何定义快速求幂算法,使之能使用任何可交换运算。使用抽象算法将能得到更高效、可靠、安全和经济的软件。 这不是一本很容易读的书,它也不是能提升你的编程技能的秘诀和技巧汇编。本书的价值是更根本性的,其终极目......一起来看看 《编程原本》 这本书的介绍吧!

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具