Skip to main content

Redux 基础知识,第 6 部分:异步逻辑和数据获取

你将学到什么
  • Redux 数据流如何处理异步数据

    ¥How the Redux data flow works with async data

  • 如何使用 Redux 中间件进行异步逻辑

    ¥How to use Redux middleware for async logic

  • 处理异步请求状态的模式

    ¥Patterns for handling async request state

先决条件
  • 熟悉使用 AJAX 请求从服务器获取和更新数据

    ¥Familiarity with using AJAX requests to fetch and update data from a server

  • 了解 JS 中的异步逻辑,包括 Promises

    ¥Understanding asynchronous logic in JS, including Promises

介绍

¥Introduction

第 5 部分:UI 和 React 中,我们了解了如何使用 React-Redux 库让我们的 React 组件与 Redux 存储交互,包括调用 useSelector 读取 Redux 状态、调用 useDispatch 使我们能够访问 dispatch 函数,以及将我们的应用封装在 <Provider> 组件中以使这些钩子能够访问存储。

¥In Part 5: UI and React, we saw how to use the React-Redux library to let our React components interact with a Redux store, including calling useSelector to read Redux state, calling useDispatch to give us access to the dispatch function, and wrapping our app in a <Provider> component to give those hooks access to the store.

到目前为止,我们使用的所有数据都直接位于 React+Redux 客户端应用内部。然而,大多数实际应用需要通过调用 HTTP API 来获取和保存项目来处理来自服务器的数据。

¥So far, all the data we've worked with has been directly inside of our React+Redux client application. However, most real applications need to work with data from a server, by making HTTP API calls to fetch and save items.

在本节中,我们将更新待办事项应用以从 API 获取待办事项,并通过将新待办事项保存到 API 来添加新待办事项。

¥In this section, we'll update our todo app to fetch the todos from an API, and add new todos by saving them to the API.

提醒

请注意,本教程有意展示旧式 Redux 逻辑模式,这些模式比我们使用 Redux Toolkit 教授的 "现代回归" 模式需要更多的代码,作为当今使用 Redux 构建应用的正确方法,以便解释 Redux 背后的原理和概念。它并不意味着是一个生产就绪的项目。

¥Note that this tutorial intentionally shows older-style Redux logic patterns that require more code than the "modern Redux" patterns with Redux Toolkit we teach as the right approach for building apps with Redux today, in order to explain the principles and concepts behind Redux. It's not meant to be a production-ready project.

请参阅以下页面以了解如何将 "现代回归" 与 Redux Toolkit 结合使用:

¥See these pages to learn how to use "modern Redux" with Redux Toolkit:

提示

Redux 工具包包括 RTK 查询数据获取和缓存 API。RTK Query 是专为 Redux 应用构建的数据获取和缓存解决方案,无需编写任何 thunk 或 reducers 来管理数据获取。我们专门教授 RTK 查询作为数据获取的默认方法,并且 RTK 查询建立在本页所示的相同模式之上。

¥Redux Toolkit includes the RTK Query data fetching and caching API. RTK Query is a purpose built data fetching and caching solution for Redux apps, and can eliminate the need to write any thunks or reducers to manage data fetching. We specifically teach RTK Query as the default approach for data fetching, and RTK Query is built on the same patterns shown in this page.

了解如何在 Redux 要点,第 7 部分:RTK 查询基础知识 中使用 RTK 查询来获取数据。

¥Learn how to use RTK Query for data fetching in Redux Essentials, Part 7: RTK Query Basics.

REST API 和客户端示例

¥Example REST API and Client

为了保持示例项目的独立性和现实性,初始项目设置已经包含了一个用于数据的虚拟内存中 REST API(使用 Mirage.js 模拟 API 工具 配置)。该 API 使用 /fakeApi 作为端点的基本 URL,并支持 /fakeApi/todos 的典型 GET/POST/PUT/DELETE HTTP 方法。它在 src/api/server.js 中定义。

¥To keep the example project isolated but realistic, the initial project setup already included a fake in-memory REST API for our data (configured using the Mirage.js mock API tool). The API uses /fakeApi as the base URL for the endpoints, and supports the typical GET/POST/PUT/DELETE HTTP methods for /fakeApi/todos. It's defined in src/api/server.js.

该项目还包括一个小型 HTTP API 客户端对象,它公开 client.get()client.post() 方法,类似于 axios 等流行的 HTTP 库。它在 src/api/client.js 中定义。

¥The project also includes a small HTTP API client object that exposes client.get() and client.post() methods, similar to popular HTTP libraries like axios. It's defined in src/api/client.js.

在本节中,我们将使用 client 对象对内存中的虚假 REST API 进行 HTTP 调用。

¥We'll use the client object to make HTTP calls to our in-memory fake REST API for this section.

Redux 中间件和副作用

¥Redux Middleware and Side Effects

Redux 存储本身并不了解异步逻辑。它只知道如何同步分派操作,通过调用根 reducer 函数更新状态,并通知 UI 某些内容发生了变化。任何异步性都必须发生在存储之外。

¥By itself, a Redux store doesn't know anything about async logic. It only knows how to synchronously dispatch actions, update the state by calling the root reducer function, and notify the UI that something has changed. Any asynchronicity has to happen outside the store.

之前,我们说过 Redux reducer 绝不能包含 "副作用"。"副作用" 是除了从函数返回值之外可以看到的状态或行为的任何更改。一些常见的副作用如下:

¥Earlier, we said that Redux reducers must never contain "side effects". A "side effect" is any change to state or behavior that can be seen outside of returning a value from a function. Some common kinds of side effects are things like:

  • 将值记录到控制台

    ¥Logging a value to the console

  • 保存文件

    ¥Saving a file

  • 设置异步计时器

    ¥Setting an async timer

  • 发出 AJAX HTTP 请求

    ¥Making an AJAX HTTP request

  • 修改函数外部存在的某些状态,或改变函数的参数

    ¥Modifying some state that exists outside of a function, or mutating arguments to a function

  • 生成随机数或唯一的随机 ID(例如 Math.random()Date.now()

    ¥Generating random numbers or unique random IDs (such as Math.random() or Date.now())

然而,任何真正的应用都需要在某个地方做这些事情。那么,如果我们不能将副作用放入 reducer 中,我们可以将它们放在哪里呢?

¥However, any real app will need to do these kinds of things somewhere. So, if we can't put side effects in reducers, where can we put them?

Redux 中间件旨在支持编写具有副作用的逻辑。

¥Redux middleware were designed to enable writing logic that has side effects.

正如我们所说的 在第 4 部分中,Redux 中间件在看到已分派的操作时可以执行任何操作:记录某些内容、修改操作、延迟操作、进行异步调用等等。另外,由于中间件围绕真实的 store.dispatch 函数形成了一个管道,这也意味着我们实际上可以将一些不是普通操作对象的东西传递给 dispatch,只要中间件拦截该值并且不让它到达 reducer。

¥As we said in Part 4, a Redux middleware can do anything when it sees a dispatched action: log something, modify the action, delay the action, make an async call, and more. Also, since middleware form a pipeline around the real store.dispatch function, this also means that we could actually pass something that isn't a plain action object to dispatch, as long as a middleware intercepts that value and doesn't let it reach the reducers.

中间件还可以访问 dispatchgetState。这意味着你可以在中间件中编写一些异步逻辑,并且仍然能够通过分派操作与 Redux 存储进行交互。

¥Middleware also have access to dispatch and getState. That means you could write some async logic in a middleware, and still have the ability to interact with the Redux store by dispatching actions.

使用中间件启用异步逻辑

¥Using Middleware to Enable Async Logic

让我们看几个示例,了解中间件如何使我们能够编写某种与 Redux 存储交互的异步逻辑。

¥Let's look at a couple examples of how middleware can enable us to write some kind of async logic that interacts with the Redux store.

一种可能性是编写一个中间件来查找特定的操作类型,并在看到这些操作时运行异步逻辑,如下例所示:

¥One possibility is writing a middleware that looks for specific action types, and runs async logic when it sees those actions, like these examples:

import { client } from '../api/client'

const delayedActionMiddleware = storeAPI => next => action => {
if (action.type === 'todos/todoAdded') {
setTimeout(() => {
// Delay this action by one second
next(action)
}, 1000)
return
}

return next(action)
}

const fetchTodosMiddleware = storeAPI => next => action => {
if (action.type === 'todos/fetchTodos') {
// Make an API call to fetch todos from the server
client.get('todos').then(todos => {
// Dispatch an action with the todos we received
storeAPI.dispatch({ type: 'todos/todosLoaded', payload: todos })
})
}

return next(action)
}
信息

有关 Redux 为什么以及如何使用中间件进行异步逻辑的更多详细信息,请参阅 Redux 创建者 Dan Abramov 的 StackOverflow 回答:

¥For more details on why and how Redux uses middleware for async logic, see these StackOverflow answers by Redux creator Dan Abramov:

编写异步函数中间件

¥Writing an Async Function Middleware

最后一节中的两个中间件都非常具体,只做一件事。如果我们有办法提前编写任何异步逻辑,与中间件本身分开,并且仍然可以访问 dispatchgetState 以便我们可以与存储交互,那就太好了。

¥Both of the middleware in that last section were very specific and only do one thing. It would be nice if we had a way to write any async logic ahead of time, separate from the middleware itself, and still have access to dispatch and getState so that we can interact with the store.

如果我们编写一个中间件,让我们将函数而不是操作对象传递给 dispatch,会怎么样?我们可以让中间件检查 "action" 是否实际上是一个函数,如果它是一个函数,则立即调用该函数。这将使我们能够在中间件定义之外的单独函数中编写异步逻辑。

¥What if we wrote a middleware that let us pass a function to dispatch, instead of an action object? We could have our middleware check to see if the "action" is actually a function instead, and if it's a function, call the function right away. That would let us write async logic in separate functions, outside of the middleware definition.

该中间件可能如下所示:

¥Here's what that middleware might look like:

Example async function middleware
const asyncFunctionMiddleware = storeAPI => next => action => {
// If the "action" is actually a function instead...
if (typeof action === 'function') {
// then call the function and pass `dispatch` and `getState` as arguments
return action(storeAPI.dispatch, storeAPI.getState)
}

// Otherwise, it's a normal action - send it onwards
return next(action)
}

然后我们可以像这样使用该中间件:

¥And then we could use that middleware like this:

const middlewareEnhancer = applyMiddleware(asyncFunctionMiddleware)
const store = createStore(rootReducer, middlewareEnhancer)

// Write a function that has `dispatch` and `getState` as arguments
const fetchSomeData = (dispatch, getState) => {
// Make an async HTTP request
client.get('todos').then(todos => {
// Dispatch an action with the todos we received
dispatch({ type: 'todos/todosLoaded', payload: todos })
// Check the updated store state after dispatching
const allTodos = getState().todos
console.log('Number of todos after loading: ', allTodos.length)
})
}

// Pass the _function_ we wrote to `dispatch`
store.dispatch(fetchSomeData)
// logs: 'Number of todos after loading: ###'

再次注意,这个 "异步函数中间件" 让我们将一个函数传递给 dispatch!在该函数内部,我们能够编写一些异步逻辑(HTTP 请求),然后在请求完成时分派一个普通的操作对象。

¥Again, notice that this "async function middleware" let us pass a function to dispatch! Inside that function, we were able to write some async logic (an HTTP request), then dispatch a normal action object when the request completed.

Redux 异步数据流

¥Redux Async Data Flow

那么中间件和异步逻辑如何影响 Redux 应用的整体数据流呢?

¥So how do middleware and async logic affect the overall data flow of a Redux app?

就像正常操作一样,我们首先需要处理应用中的用户事件,例如单击按钮。然后,我们调用 dispatch(),并传入一些内容,无论是普通的操作对象、函数还是中间件可以查找的其他值。

¥Just like with a normal action, we first need to handle a user event in the application, such as a click on a button. Then, we call dispatch(), and pass in something, whether it be a plain action object, a function, or some other value that a middleware can look for.

一旦分派的值到达中间件,它就可以进行异步调用,然后在异步调用完成时分派真正的操作对象。

¥Once that dispatched value reaches a middleware, it can make an async call, and then dispatch a real action object when the async call completes.

早些时候,我们看到了 表示正常同步 Redux 数据流的图表。当我们向 Redux 应用添加异步逻辑时,我们添加了一个额外的步骤,其中中间件可以运行 AJAX 请求等逻辑,然后调度操作。这使得异步数据流看起来像这样:

¥Earlier, we saw a diagram that represents the normal synchronous Redux data flow. When we add async logic to a Redux app, we add an extra step where middleware can run logic like AJAX requests, then dispatch actions. That makes the async data flow look like this:

Redux async data flow diagram

使用 Redux Thunk 中间件

¥Using the Redux Thunk Middleware

事实证明,Redux 已经有了 "异步函数中间件" 的官方版本,称为 Redux "重击" 中间件。thunk 中间件允许我们编写以 dispatchgetState 作为参数的函数。thunk 函数可以包含我们想要的任何异步逻辑,并且该逻辑可以根据需要分派操作并读取存储状态。

¥As it turns out, Redux already has an official version of that "async function middleware", called the Redux "Thunk" middleware. The thunk middleware allows us to write functions that get dispatch and getState as arguments. The thunk functions can have any async logic we want inside, and that logic can dispatch actions and read the store state as needed.

将异步逻辑编写为 thunk 函数允许我们重用该逻辑,而无需提前知道我们正在使用什么 Redux 存储。

¥Writing async logic as thunk functions allows us to reuse that logic without knowing what Redux store we're using ahead of time.

信息

"thunk" 一词是一个编程术语,意思是 "一段执行一些延迟工作的代码"。有关如何使用 thunk 的更多详细信息,请参阅 thunk 使用指南页面:

¥The word "thunk" is a programming term that means "a piece of code that does some delayed work". For more details on how to use thunks, see the thunk usage guide page:

以及这些帖子:

¥as well as these posts:

配置存储

¥Configuring the Store

Redux thunk 中间件在 NPM 上作为名为 redux-thunk 的包提供。我们需要安装该包才能在我们的应用中使用它:

¥The Redux thunk middleware is available on NPM as a package called redux-thunk. We need to install that package to use it in our app:

npm install redux-thunk

安装后,我们可以更新 todo 应用中的 Redux 存储以使用该中间件:

¥Once it's installed, we can update the Redux store in our todo app to use that middleware:

src/store.js
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from './reducer'

const composedEnhancer = composeWithDevTools(applyMiddleware(thunkMiddleware))

// The store now has the ability to accept thunk functions in `dispatch`
const store = createStore(rootReducer, composedEnhancer)
export default store

从服务器获取待办事项

¥Fetching Todos from a Server

现在我们的待办事项条目只能存在于客户端的浏览器中。我们需要一种在应用启动时从服务器加载待办事项列表的方法。

¥Right now our todo entries can only exist in the client's browser. We need a way to load a list of todos from the server when the app starts up.

我们将首先编写一个 thunk 函数,该函数对 /fakeApi/todos 端点进行 AJAX 调用以请求待办事项对象数组,然后分派包含该数组作为有效负载的操作。由于这通常与 todos 功能相关,因此我们将 thunk 函数写入 todosSlice.js 文件中:

¥We'll start by writing a thunk function that makes an AJAX call to our /fakeApi/todos endpoint to request an array of todo objects, and then dispatch an action containing that array as the payload. Since this is related to the todos feature in general, we'll write the thunk function in the todosSlice.js file:

src/features/todos/todosSlice.js
import { client } from '../../api/client'

const initialState = []

export default function todosReducer(state = initialState, action) {
// omit reducer logic
}

// Thunk function
export async function fetchTodos(dispatch, getState) {
const response = await client.get('/fakeApi/todos')
dispatch({ type: 'todos/todosLoaded', payload: response.todos })
}

我们只想在应用首次加载时调用此 API 一次。我们可以把它放在几个地方:

¥We only want to make this API call once, when the application loads for the first time. There's a few places we could put this:

  • <App> 组件中,在 useEffect 钩子中

    ¥In the <App> component, in a useEffect hook

  • <TodoList> 组件中,在 useEffect 钩子中

    ¥In the <TodoList> component, in a useEffect hook

  • 直接在 index.js 文件中,就在我们导入存储之后

    ¥In the index.js file directly, right after we import the store

现在,我们尝试将其直接放入 index.js 中:

¥For now, let's try putting this directly in index.js:

src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import './index.css'
import App from './App'

import './api/server'

import store from './store'
import { fetchTodos } from './features/todos/todosSlice'

store.dispatch(fetchTodos)

ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
)

如果我们重新加载页面,用户界面中不会出现明显的变化。但是,如果我们打开 Redux DevTools 扩展,我们现在应该看到已调度 'todos/todosLoaded' 操作,并且它应该包含一些由我们的假服务器 API 生成的 todo 对象:

¥If we reload the page, there's no visible change in the UI. However, if we open up the Redux DevTools extension, we should now see that a 'todos/todosLoaded' action was dispatched, and it should contain some todo objects that were generated by our fake server API:

Devtools - todosLoaded action contents

请注意,即使我们已经发送了一个操作,也没有发生任何事情来改变状态。我们需要在 todos reducer 中处理此操作以更新状态。

¥Notice that even though we've dispatched an action, nothing's happening to change the state. We need to handle this action in our todos reducer to have the state updated.

让我们向 reducer 添加一个案例,以将此数据加载到存储中。由于我们从服务器获取数据,因此我们希望完全替换任何现有的待办事项,因此我们可以返回 action.payload 数组以使其成为新的待办事项 state 值:

¥Let's add a case to the reducer to load this data into the store. Since we're fetching the data from the server, we want to completely replace any existing todos, so we can return the action.payload array to make it be the new todos state value:

src/features/todos/todosSlice.js
import { client } from '../../api/client'

const initialState = []

export default function todosReducer(state = initialState, action) {
switch (action.type) {
// omit other reducer cases
case 'todos/todosLoaded': {
// Replace the existing state entirely by returning the new value
return action.payload
}
default:
return state
}
}

export async function fetchTodos(dispatch, getState) {
const response = await client.get('/fakeApi/todos')
dispatch({ type: 'todos/todosLoaded', payload: response.todos })
}

由于分派动作会立即更新存储,因此我们还可以在分派后调用 thunk 中的 getState 来读取更新后的状态值。例如,我们可以在调度 'todos/todosLoaded' 操作之前和之后将待办事项总数记录到控制台:

¥Since dispatching an action immediately updates the store, we can also call getState in the thunk to read the updated state value after we dispatch. For example, we could log the number of total todos to the console before and after dispatching the 'todos/todosLoaded' action:

export async function fetchTodos(dispatch, getState) {
const response = await client.get('/fakeApi/todos')

const stateBefore = getState()
console.log('Todos before dispatch: ', stateBefore.todos.length)

dispatch({ type: 'todos/todosLoaded', payload: response.todos })

const stateAfter = getState()
console.log('Todos after dispatch: ', stateAfter.todos.length)
}

保存待办事项

¥Saving Todo Items

每当我们尝试创建新的待办事项时,我们还需要更新服务器。我们不应该立即分派 'todos/todoAdded' 操作,而应该使用初始数据对服务器进行 API 调用,等待服务器发回新保存的待办事项的副本,然后使用该待办事项分派一个操作。

¥We also need to update the server whenever we try to create a new todo item. Instead of dispatching the 'todos/todoAdded' action right away, we should make an API call to the server with the initial data, wait for the server to send back a copy of the newly saved todo item, and then dispatch an action with that todo item.

然而,如果我们开始尝试将此逻辑编写为 thunk 函数,我们将遇到一个问题:由于我们将 thunk 作为单独的函数编写在 todosSlice.js 文件中,因此进行 API 调用的代码不知道新的待办事项文本应该是什么:

¥However, if we start trying to write this logic as a thunk function, we're going to run into a problem: since we're writing the thunk as a separate function in the todosSlice.js file, the code that makes the API call doesn't know what the new todo text is supposed to be:

src/features/todos/todosSlice.js
async function saveNewTodo(dispatch, getState) {
// ❌ We need to have the text of the new todo, but where is it coming from?
const initialTodo = { text }
const response = await client.post('/fakeApi/todos', { todo: initialTodo })
dispatch({ type: 'todos/todoAdded', payload: response.todo })
}

我们需要一种方法来编写一个接受 text 作为参数的函数,然后创建实际的 thunk 函数,以便它可以使用 text 值来进行 API 调用。然后,我们的外部函数应该返回 thunk 函数,以便我们可以传递给组件中的 dispatch

¥We need a way to write one function that accepts text as its parameter, but then creates the actual thunk function so that it can use the text value to make the API call. Our outer function should then return the thunk function so that we can pass to dispatch in our component.

src/features/todos/todosSlice.js
// Write a synchronous outer function that receives the `text` parameter:
export function saveNewTodo(text) {
// And then creates and returns the async thunk function:
return async function saveNewTodoThunk(dispatch, getState) {
// ✅ Now we can use the text value and send it to the server
const initialTodo = { text }
const response = await client.post('/fakeApi/todos', { todo: initialTodo })
dispatch({ type: 'todos/todoAdded', payload: response.todo })
}
}

现在我们可以在 <Header> 组件中使用它:

¥Now we can use this in our <Header> component:

src/features/header/Header.js
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'

import { saveNewTodo } from '../todos/todosSlice'

const Header = () => {
const [text, setText] = useState('')
const dispatch = useDispatch()

const handleChange = e => setText(e.target.value)

const handleKeyDown = e => {
// If the user pressed the Enter key:
const trimmedText = text.trim()
if (e.which === 13 && trimmedText) {
// Create the thunk function with the text the user wrote
const saveNewTodoThunk = saveNewTodo(trimmedText)
// Then dispatch the thunk function itself
dispatch(saveNewTodoThunk)
setText('')
}
}

// omit rendering output
}

因为我们知道我们将立即将 thunk 函数传递给组件中的 dispatch,所以我们可以跳过创建临时变量。相反,我们可以调用 saveNewTodo(text),并将生成的 thunk 函数直接传递给 dispatch

¥Since we know we're going to immediately pass the thunk function to dispatch in the component, we can skip creating the temporary variable. Instead, we can call saveNewTodo(text), and pass the resulting thunk function straight to dispatch:

src/features/header/Header.js
const handleKeyDown = e => {
// If the user pressed the Enter key:
const trimmedText = text.trim()
if (e.which === 13 && trimmedText) {
// Create the thunk function and immediately dispatch it
dispatch(saveNewTodo(trimmedText))
setText('')
}
}

现在组件实际上并不知道它正在调度一个 thunk 函数 - saveNewTodo 函数封装了实际发生的情况。<Header> 组件只知道当用户按 Enter 时它需要分派一些值。

¥Now the component doesn't actually know that it's even dispatching a thunk function - the saveNewTodo function is encapsulating what's actually happening. The <Header> component only knows that it needs to dispatch some value when the user presses enter.

这种编写函数来准备将传递给 dispatch 的内容的模式称为 "动作创造者" 模式,我们将在 下一节 中详细讨论该模式。

¥This pattern of writing a function to prepare something that will get passed to dispatch is called the "action creator" pattern, and we'll talk about that more in the next section.

我们现在可以看到正在调度更新的 'todos/todoAdded' 操作:

¥We can now see the updated 'todos/todoAdded' action being dispatched:

Devtools - async todoAdded action contents

我们需要改变的最后一件事是更新我们的 todos reducer。当我们向 /fakeApi/todos 发出 POST 请求时,服务器将返回一个全新的 todo 对象(包括新的 ID 值)。这意味着我们的 reducer 不必计算新的 ID,或填写其他字段 - 它只需要创建一个新的 state 数组,其中包含新的待办事项:

¥The last thing we need to change here is updating our todos reducer. When we make a POST request to /fakeApi/todos, the server will return a completely new todo object (including a new ID value). That means our reducer doesn't have to calculate a new ID, or fill out the other fields - it only needs to create a new state array that includes the new todo item:

src/features/todos/todosSlice.js
const initialState = []

export default function todosReducer(state = initialState, action) {
switch (action.type) {
case 'todos/todoAdded': {
// Return a new todos state array with the new todo item at the end
return [...state, action.payload]
}
// omit other cases
default:
return state
}
}

现在添加一个新的待办事项将可以正常工作:

¥And now adding a new todo will work correctly:

Devtools - async todoAdded state diff

提示

Thunk 函数可用于异步和同步逻辑。Thunk 提供了一种编写任何需要访问 dispatchgetState 的可重用逻辑的方法。

¥Thunk functions can be used for both asynchronous and synchronous logic. Thunks provide a way to write any reusable logic that needs access to dispatch and getState.

你学到了什么

¥What You've Learned

我们现在已经成功更新了我们的待办事项应用,以便我们可以获取待办事项列表并保存新的待办事项,使用 "thunk" 函数对我们的假服务器 API 进行 AJAX 调用。

¥We've now successfully updated our todo app so that we can fetch a list of todo items and save new todo items, using "thunk" functions to make the AJAX calls to our fake server API.

在此过程中,我们了解了如何使用 Redux 中间件来进行异步调用,并通过在异步调用完成后分派操作来与存储进行交互。

¥In the process, we saw how Redux middleware are used to let us make async calls and interact with the store by dispatching actions with after the async calls have completed.

当前应用如下所示:

¥Here's what the current app looks like:

概括
  • Redux 中间件旨在支持编写具有副作用的逻辑

    ¥Redux middleware were designed to enable writing logic that has side effects

    • "副作用" 是在函数外部更改状态/行为的代码,例如 AJAX 调用、修改函数参数或生成随机值

      ¥"Side effects" are code that changes state/behavior outside a function, like AJAX calls, modifying function arguments, or generating random values

  • 中间件向标准 Redux 数据流添加了额外的步骤

    ¥Middleware add an extra step to the standard Redux data flow

    • 中间件可以拦截传递给 dispatch 的其他值

      ¥Middleware can intercept other values passed to dispatch

    • 中间件可以访问 dispatchgetState,因此它们可以分派更多操作作为异步逻辑的一部分

      ¥Middleware have access to dispatch and getState, so they can dispatch more actions as part of async logic

  • Redux "重击" 中间件让我们可以将函数传递给 dispatch

    ¥The Redux "Thunk" middleware lets us pass functions to dispatch

    • "重击" 函数让我们提前编写异步逻辑,而无需知道正在使用什么 Redux 存储

      ¥"Thunk" functions let us write async logic ahead of time, without knowing what Redux store is being used

    • Redux thunk 函数接收 dispatchgetState 作为参数,并且可以分派诸如 "该数据是从 API 响应收到的" 之类的操作

      ¥A Redux thunk function receives dispatch and getState as arguments, and can dispatch actions like "this data was received from an API response"

下一步是什么?

¥What's Next?

我们现在已经涵盖了如何使用 Redux 的所有核心部分!你已经了解了如何:

¥We've now covered all the core pieces of how to use Redux! You've seen how to:

  • 编写根据分派操作更新状态的 reducer,

    ¥Write reducers that update state based on dispatched actions,

  • 使用缩减器、增强器和中间件创建和配置 Redux 存储

    ¥Create and configure a Redux store with a reducer, enhancers, and middleware

  • 使用中间件编写调度操作的异步逻辑

    ¥Use middleware to write async logic that dispatches actions

第 7 部分:标准 Redux 模式 中,我们将研究现实 Redux 应用通常使用的几种代码模式,以使我们的代码更加一致,并随着应用的增长更好地扩展。

¥In Part 7: Standard Redux Patterns, we'll look at several code patterns that are typically used by real-world Redux apps to make our code more consistent and scale better as the application grows.