Redux 进阶:中间件的使用

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

内容简介:用过 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 进阶:中间件的使用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

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

算法设计与实验题解

算法设计与实验题解

王晓东 / 电子工业 / 2006-9 / 46.00元

《算法设计与实验题解》是与普通高等教育“十一五”国家级规划教材《计算机算法设计与分析》(第2版)配套的辅助教材,对主教材中的全部习题做了解答或给出了解题思路提示,并对主教材的内容进行了扩展,有些主教材中无法讲述的较深入的主题以习题的形式展现出来。为了提高学生解决实际问题的能力,《算法设计与实验题解》还将主教材中的许多习题改造成算法实现题,要求学生设计出算法并上机实现。作者还结合精品课程建设,进行了......一起来看看 《算法设计与实验题解》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具