Redux 进阶:中间件的使用

news/2024/11/5 10:30:18 标签: ui, javascript, json

什么是 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;
    }
 }

结果

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 看起来就会类似这样:
javascript">function logger(next) {
  return action => {
    console.log('在这里中一些额外的工作')
    return next(action)
  }
}

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

由于 store 中类似 getState() 的方法依旧非常有用,我们将 store 作为顶层的参数,使得它可以在所有 middleware 中被使用。这样的话,一个 middleware 的 API 最终看起来就变成这样:
javascript">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


http://www.niftyadmin.cn/n/1335568.html

相关文章

注册 CrashHandler crashHandler = CrashHandler.getInstance();

注册 CrashHandler crashHandler CrashHandler.getInstance(); MainActivity如下:[java] package cn.testcrash; import android.app.Activity; import android.os.Bundle; /*** Demo描述:* 借助于Application自定义Crash* * 备注说明:* 1 在获取Crash信息后可依据业务上传…

还在让WiFi被人蹭网?学会这四招,没人蹭的WiFi网速至少快三倍

不知道小伙伴们有没有这样的困扰&#xff0c;自己的手机WiFi信号明明满格&#xff0c;为什么手机却还是卡的要死&#xff0c;难道又要换手机了&#xff1f;还是说运营商在嫉妒我&#xff1f;其实这是隔壁老王在蹭你的网&#xff0c;那么遇到这种情况你该怎么办呢&#xff1f; 今…

NDK编译OpenSSL

Openssl-1.1.1d 和此前的其他编译不同&#xff0c;不要任何初始化脚本&#xff0c;运行了反而会报错。 因此需要新开窗口编译,。 ANDROID版本默认会用最高的那个版本&#xff0c;因此需要指定。 export ANDROID_NDK_HOME/opt/android-ndk-r14b ./Configure android-arm --pr…

Visual Studio2017 离线安装

Visual Studio2017的离线安装需要使用命令行来创建本地缓存 下载引导程序后&#xff0c;使用命令行来创建本地缓存&#xff0c;然后用本地缓存安装Visual Studio 步骤1-下载Visual Studio引导程序 选择你想要下载的版本&#xff0c;点此进入进行选择下载 版本FileVisual Studio…

Excel中数据也能自己朗读?这一个操作太神奇了,赶紧学起来

世界之大&#xff0c;无奇不有&#xff0c;很多实用的小技巧&#xff0c;可能在你不知道的情况下&#xff0c;别人就已经开始使用了&#xff0c;当然&#xff0c;也有很多功能是你熟悉&#xff0c;而别人不知道的&#xff0c;所以呀&#xff0c;我们需要不断的补充能量&#xf…

ServiceConnection接口的使用

首先是Service的周期问题 The service will at this point continue running until Context.stopService() or stopSelf() is called. Note that multiple calls to Context.startService() do not nest (though they do result in multiple corresponding calls to onStartCom…

Mysql自动按月表分区

很久没有碰DB了。最近需要做一个日志表&#xff0c;因此重新拾了下。实现了下自动按月表分区 开发环境为Mysql 5.7.28 参考资料&#xff1a; Mysql分区表及自动创建分区Partition&#xff08;按日&#xff09; 核心的两个存储过程&#xff1a; auto_create_partition为创建表…

小程序创业最后的红利期

从 2017 年初&#xff0c;微信创始人张小龙宣布小程序正式上线开始&#xff0c;很多行业都在摸索着开发小程序&#xff0c;且取得了不错的成绩&#xff0c;小程序已经生长出一个相对完整的生态圈。 在这个生态圈内&#xff0c;基于去中心化的特点&#xff0c;依托微信入口省去了…