返回介绍

十、中间件与异步操作

发布于 2024-09-07 17:54:34 字数 7116 浏览 0 评论 0 收藏 0

Redux 的基本做法:用户发出 ActionReducer 函数算出新的 StateView 重新渲染

  • 一个关键问题没有解决:异步操作怎么办? Action 发出以后, Reducer 立即算出 State ,这叫做同步; Action 发出以后,过一段时间再执行 Reducer ,这就是异步
  • 怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件( middleware

10.1 中间件的概念

中间件就是一个函数,对 store.dispatch 方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

10.2 中间件的用法

常用的中间件都有现成的,只要引用别人写好的模块即可。比如日志中间件,就有现成的 redux-logger 模块

import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();

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

上面代码中, redux-logger 提供一个生成器 createLogger ,可以生成日志中间件 logger 。然后,将它放在 applyMiddleware 方法之中,传入 createStore 方法,就完成了 store.dispatch() 的功能增强

这里有两点需要注意

  • (1) createStore 方法可以接受整个应用的初始状态作为参数,那样的话, applyMiddleware 就是第三个参数了
const store = createStore(
  reducer,
  initial_state,
  applyMiddleware(logger)
);
  • (2)中间件的次序有讲究
const store = createStore(
  reducer,
  applyMiddleware(thunk, promise, logger)
);

上面代码中, applyMiddleware 方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如, logger 就一定要放在最后,否则输出结果会不正确

10.3、applyMiddlewares()

applyMiddlewares 这个方法。它是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行

10.4 异步操作的基本思路

理解了中间件以后,就可以处理异步操作了

  • 同步操作只要发出一种 Action 即可,异步操作的差别是它要发出三种 Action
    • 操作发起时的 Action
    • 操作成功时的 Action
    • 操作失败时的 Action

以向服务器取出数据为例,三种 Action 可以有两种不同的写法

// 写法一:名称相同,参数不同
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }

// 写法二:名称不同
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }

除了 Action 种类不同,异步操作的 State 也要进行改造,反映不同的操作状态。下面是 State 的一个例子

let state = {
  // ... 
  isFetching: true,
  didInvalidate: true,
  lastUpdated: 'xxxxxxx'
};

上面代码中, State 的属性 isFetching 表示是否在抓取数据。 didInvalidate 表示数据是否过时, lastUpdated 表示上一次更新时间

现在,整个异步操作的思路就很清楚了

  • 操作开始时,送出一个 Action ,触发 State 更新为”正在操作”状态, View 重新渲染
  • 操作结束后,再送出一个 Action ,触发 State 更新为”操作结束”状态, View 再一次重新渲染

10.5 redux-thunk 中间件

异步操作至少要送出两个 Action :用户触发第一个 Action ,这个跟同步操作一样,没有问题;如何才能在操作结束时,系统自动送出第二个 Action

  • 奥妙就在 Action Creator 之中
class AsyncApp extends Component {
  componentDidMount() {
    const { dispatch, selectedPost } = this.props
    dispatch(fetchPosts(selectedPost))
  }

// ...

上面代码是一个异步组件的例子。加载成功后( componentDidMount 方法),它送出了( dispatch 方法)一个 Action ,向服务器要求数据 fetchPosts(selectedSubreddit) 。这里的 fetchPosts 就是 Action Creator

  • 下面就是 fetchPosts 的代码,关键之处就在里面
const fetchPosts = postTitle => (dispatch, getState) => {
  dispatch(requestPosts(postTitle));
  return fetch(`/some/API/${postTitle}.json`)
    .then(response => response.json())
    .then(json => dispatch(receivePosts(postTitle, json)));
  };
};

// 使用方法一
store.dispatch(fetchPosts('reactjs'));
// 使用方法二
store.dispatch(fetchPosts('reactjs')).then(() =>
  console.log(store.getState())
);

上面代码中, fetchPosts 是一个 Action Creator (动作生成器),返回一个函数。这个函数执行后,先发出一个 Action(requestPosts(postTitle) ),然后进行异步操作。拿到结果后,先将结果转成 JSON 格式,然后再发出一个 Action( receivePosts(postTitle, json)

上面代码中,有几个地方需要注意

  • fetchPosts 返回了一个函数,而普通的 Action Creator 默认返回一个对象
  • 返回的函数的参数是 dispatchgetState 这两个 Redux 方法,普通的 Action Creator 的参数是 Action 的内容
  • 在返回的函数之中,先发出一个 Action(requestPosts(postTitle)) ,表示操作开始
  • 异步操作结束之后,再发出一个 Action(receivePosts(postTitle, json)) ,表示操作结束

这样的处理,就解决了自动发送第二个 Action 的问题。但是,又带来了一个新的问题, Action 是由 store.dispatch 方法发送的。而 store.dispatch 方法正常情况下,参数只能是对象,不能是函数

  • 这时,就要使用中间件 redux-thunk
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';

// Note: this API requires redux@>=3.1.0
const store = createStore(
  reducer,
  applyMiddleware(thunk)
);
  • 上面代码使用 redux-thunk 中间件,改造 store.dispatch ,使得后者可以接受函数作为参数

因此,异步操作的第一种解决方案就是,写出一个返回函数的 Action Creator ,然后使用 redux-thunk 中间件改造 store.dispatch

10.6、redux-promise 中间件


既然 Action Creator 可以返回函数,当然也可以返回其他值。另一种异步操作的解决方案,就是让 Action Creator 返回一个 Promise 对象

  • 这就需要使用 redux-promise 中间件
import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise';
import reducer from './reducers';

const store = createStore(
  reducer,
  applyMiddleware(promiseMiddleware)
);

这个中间件使得 store.dispatch 方法可以接受 Promise 对象作为参数。这时, Action Creator 有两种写法

  • 写法一,返回值是一个 Promise 对象
const fetchPosts = 
  (dispatch, postTitle) => new Promise(function (resolve, reject) {
     dispatch(requestPosts(postTitle));
     return fetch(`/some/API/${postTitle}.json`)
       .then(response => {
         type: 'FETCH_POSTS',
         payload: response.json()
       });
});
  • 写法二, Action 对象的 payload 属性是一个 Promise 对象。这需要从 redux-actions 模块引入 createAction 方法,并且写法也要变成下面这样
import { createAction } from 'redux-actions';

class AsyncApp extends Component {
  componentDidMount() {
    const { dispatch, selectedPost } = this.props
    // 发出同步 Action
    dispatch(requestPosts(selectedPost));
    // 发出异步 Action
    dispatch(createAction(
      'FETCH_POSTS', 
      fetch(`/some/API/${postTitle}.json`)
        .then(response => response.json())
    ));
  }
  • 上面代码中,第二个 dispatch 方法发出的是异步 Action ,只有等到操作结束,这个 Action 才会实际发出
  • 注意, createAction 的第二个参数必须是一个 Promise 对象

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文