介绍
默认情况下,Redux的操作是同步发送的,这对于任何需要与外部API进行通信或执行副作用的非微型应用来说都是一个问题。
有两个非常流行的中间软件库,允许副作用和不同步操作: Redux Thunk和 Redux 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-redux
和axios
。
在这里没有解释如何从头开始构建Todo应用程序的细节,而是作为一个概念设置来突出Redux Thunk。
更多关于redux-thunk
首先,使用终端导航到项目目录并在项目中安装redux-thunk
包:
1npm install [email protected]
<$>[注] 注: Redux Thunk 只有 14 行代码. 查看 来源在这里 了解 Redux 中间件如何在帽子下工作。
现在,在使用Redux的applyMiddleware
创建应用商店时,使用中间软件。 由于React应用程序具有redux
和react-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);
此操作将使用 Axios将POST
请求发送到 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 主题页面以获取练习和编程项目。