使用 Redux Thunk 理解异步 Redux 操作

介绍

默认情况下,Redux的操作是同步发送的,这对于任何需要与外部API进行通信或执行副作用的非微型应用来说都是一个问题。

有两个非常流行的中间软件库,允许副作用和不同步操作: Redux ThunkRedux Saga

Thunk是一个编程概念,其中一个函数被用来延迟操作的评估/计算。

Redux Thunk 是一个中间软件,允许您调用返回函数而不是动作对象的动作创建器,该函数接收了商店的发送方法,然后用来发送函数内部的正常同步操作,一旦非同步操作完成。

在本文中,您将了解如何添加 Redux Thunk 以及它如何适合假设的 Todo 应用程序。

前提条件

这个帖子假定你对React和Redux有一定的基本知识,如果你开始使用Redux,你可以(https://andsky.com/tech/tutorials/react-react-redux)参考这个帖子。

本教程是由一个假设的Todo应用程序构建的,该应用程序跟踪需要完成且已完成的任务,我们可以假设create-react-app用于生成一个新的React应用程序,并且已经安装了redux,react-reduxaxios

在这里没有解释如何从头开始构建Todo应用程序的细节,而是作为一个概念设置来突出Redux Thunk。

更多关于redux-thunk

首先,使用终端导航到项目目录并在项目中安装redux-thunk包:

1npm install [email protected]

<$>[注] : Redux Thunk 只有 14 行代码. 查看 来源在这里 了解 Redux 中间件如何在帽子下工作。

现在,在使用Redux的applyMiddleware创建应用商店时,使用中间软件。 由于React应用程序具有reduxreact-redux,您的index.js文件可能看起来像这样:

 1[label src/index.js]
 2import React from 'react';
 3import ReactDOM from 'react-dom';
 4import { Provider } from 'react-redux';
 5import { createStore, applyMiddleware } from 'redux';
 6import thunk from 'redux-thunk';
 7import './index.css';
 8import rootReducer from './reducers';
 9import App from './App';
10import * as serviceWorker from './serviceWorker';
11
12// use applyMiddleware to add the thunk middleware to the store
13const store = createStore(rootReducer, applyMiddleware(thunk));
14
15ReactDOM.render(
16  <Provider store={store}>
17    <App />
18  </Provider>,
19  document.getElementById('root')
20);

现在,Redux Thunk被导入并应用于您的应用程序中。

在样本应用中使用 Redux Thunk

Redux Thunk 最常见的用途是与外部 API 同步通信,以获取或保存数据。

创建一个新的 todo 项目通常包括先发送一个操作,表示 todo 项目的创建已经开始,然后,如果 todo 项目成功创建并由外部服务器返回,发送新的 todo 项目的另一个操作。

让我们看看 Redux Thunk 是如何实现的。

在您的容器组件中,导入操作并发送它:

 1[label src/containers/AddTodo.js]
 2import { connect } from 'react-redux';
 3import { addTodo } from '../actions';
 4import NewTodo from '../components/NewTodo';
 5
 6const mapDispatchToProps = dispatch => {
 7  return {
 8    onAddTodo: todo => {
 9      dispatch(addTodo(todo));
10    }
11  };
12};
13
14export default connect(
15  null,
16  mapDispatchToProps
17)(NewTodo);

此操作将使用 AxiosPOST请求发送到 JSONPlaceholder的终端(https://jsonplaceholder.typicode.com/todos):

 1[label src/actions/index.js]
 2import {
 3  ADD_TODO_SUCCESS,
 4  ADD_TODO_FAILURE,
 5  ADD_TODO_STARTED,
 6  DELETE_TODO
 7} from './types';
 8
 9import axios from 'axios';
10
11export const addTodo = ({ title, userId }) => {
12  return dispatch => {
13    dispatch(addTodoStarted());
14
15    axios
16      .post(`https://jsonplaceholder.typicode.com/todos`, {
17        title,
18        userId,
19        completed: false
20      })
21      .then(res => {
22        dispatch(addTodoSuccess(res.data));
23      })
24      .catch(err => {
25        dispatch(addTodoFailure(err.message));
26      });
27  };
28};
29
30const addTodoSuccess = todo => ({
31  type: ADD_TODO_SUCCESS,
32  payload: {
33    ...todo
34  }
35});
36
37const addTodoStarted = () => ({
38  type: ADD_TODO_STARTED
39});
40
41const addTodoFailure = error => ({
42  type: ADD_TODO_FAILURE,
43  payload: {
44    error
45  }
46});

注意addTodo动作创建器如何返回函数而不是常规动作对象。

在函数的体内,您首先将即时同步操作发送到商店,以表示您已经开始使用外部 API 保存 todo。然后您将实际的POST请求发送到服务器使用 Axios. 在从服务器成功响应时,您将发送来自响应的数据的同步成功操作,但在响应失败时,我们将与错误消息发送不同的同步操作。

但是,如果您正在使用本地后端服务器,网络响应可能会发生太快,以便体验到实际用户所经历的网络延迟,因此在开发时可以添加一些人工延迟:

 1[label src/actions/index.js]
 2// ...
 3
 4export const addTodo = ({ title, userId }) => {
 5  return dispatch => {
 6    dispatch(addTodoStarted());
 7
 8    axios
 9      .post(ENDPOINT, {
10        title,
11        userId,
12        completed: false
13      })
14      .then(res => {
15        setTimeout(() => {
16          dispatch(addTodoSuccess(res.data));
17        }, 2500);
18      })
19      .catch(err => {
20        dispatch(addTodoFailure(err.message));
21      });
22  };
23};
24
25// ...

要测试错误场景,您可以手动投放错误:

 1[label src/actions/index.js]
 2// ...
 3
 4export const addTodo = ({ title, userId }) => {
 5  return dispatch => {
 6    dispatch(addTodoStarted());
 7
 8    axios
 9      .post(ENDPOINT, {
10        title,
11        userId,
12        completed: false
13      })
14      .then(res => {
15        throw new Error('addToDo error!');
16        // dispatch(addTodoSuccess(res.data));
17      })
18      .catch(err => {
19        dispatch(addTodoFailure(err.message));
20      });
21  };
22};
23
24// ...

对于完整性来说,这里有一个例子表明,todo 减速器可以如何处理请求的整个生命周期:

 1[label src/reducers/todosReducer.js]
 2import {
 3  ADD_TODO_SUCCESS,
 4  ADD_TODO_FAILURE,
 5  ADD_TODO_STARTED,
 6  DELETE_TODO
 7} from '../actions/types';
 8
 9const initialState = {
10  loading: false,
11  todos: [],
12  error: null
13};
14
15export default function todosReducer(state = initialState, action) {
16  switch (action.type) {
17    case ADD_TODO_STARTED:
18      return {
19        ...state,
20        loading: true
21      };
22    case ADD_TODO_SUCCESS:
23      return {
24        ...state,
25        loading: false,
26        error: null,
27        todos: [...state.todos, action.payload]
28      };
29    case ADD_TODO_FAILURE:
30      return {
31        ...state,
32        loading: false,
33        error: action.payload.error
34      };
35    default:
36      return state;
37  }
38}

探索GetState

除了从状态接收发送方法外,使用Redux Thunk的非同步动作创建器返回的函数还接收了商店的getState方法,以便可以读取当前的商店值:

 1[label src/actions/index.js]
 2export const addTodo = ({ title, userId }) => {
 3  return (dispatch, getState) => {
 4    dispatch(addTodoStarted());
 5
 6    console.log('current state:', getState());
 7
 8    // ...
 9  };
10};

有了上述情况,当前状态只会被打印到控制台上。

例如:

1{loading: true, todos: Array(1), error: null}

例如,如果您想将应用程序限制为一次仅四个 todo 项目,您可以退出函数,如果该状态已经包含最大数量的 todo 项目:

 1[label src/actions/index.js]
 2export const addTodo = ({ title, userId }) => {
 3  return (dispatch, getState) => {
 4    const { todos } = getState();
 5
 6    if (todos.length > 4) return;
 7
 8    dispatch(addTodoStarted());
 9
10    // ...
11  };
12};

随着上述情况,应用程序将被限制在四个 Todo 项目中。

结论

在本教程中,您探索了将 Redux Thunk 添加到 React 应用程序中,以便在使用 Redux 商店和依赖外部 API 时允许非同步发送操作。

如果您想了解更多关于 React 的信息,请查看我们的 如何在 React.js 中编码系列,或查看 我们的 React 主题页面以获取练习和编程项目。

Published At
Categories with 技术
comments powered by Disqus