5.基础教程-异步逻辑与数据请求

你将学到
  • 如何使用 Redux “thunk” middleware 处理异步逻辑
  • 处理异步请求状态的开发模式
  • 如何使用 Redux Toolkit createAsyncThunk API 来简化异步调用

thunks 与异步逻辑

使用 Middleware 处理异步逻辑

就其本身而言,Redux store 对异步逻辑一无所知,任何异步都必须发生在 store 之外。

Redux middleware 扩展了 store,它允许:

  • dispatch action 时执行额外的逻辑(例如打印 action 的日志和状态)
  • 暂停、修改、延迟、替换或停止 dispatch 的 action
  • 编写可以访问 dispatchgetState 的额外代码
  • dispatch 如何接受除普通 action 对象之外的其他值,例如函数和 promise,通过拦截它们并 dispatch 实际 action 对象来代替

使用 middleware 的最常见原因是允许不同类型的异步逻辑与 store 交互。这允许你编写可以 dispatch action 和检查 store 状态的代码,同时使该逻辑与你的 UI 分开。

Redux 有多种异步 middleware,每一种都允许你使用不同的语法编写逻辑。最常见的异步 middleware 是 redux-thunk,它可以让你编写可能直接包含异步逻辑的普通函数。Redux Toolkit 的 configureStore 功能默认自动设置 thunk middleware我们推荐使用 thunk 作为 Redux 开发异步逻辑的标准方式

早些时候,我们看到了Redux 的同步数据流是什么样子。当引入异步逻辑时,我们添加了一个额外的步骤,middleware 可以运行像 AJAX 请求这样的逻辑,然后 dispatch action。这使得异步数据流看起来像这样:

thunk 函数

将 thunk middleware 添加到 Redux store 后,它允许你将 thunk 函数 直接传递给 store.dispatch。调用 thunk 函数时总是将 (dispatch, getState) 作为它的参数,你可以根据需要在 thunk 中使用它们。

Thunks 通常还可以使用 action creator 再次 dispatch 普通的 action,比如 dispatch(increment())

1
2
3
4
5
6
7
8
9
10
11
12
const store = configureStore({ reducer: counterReducer })

// thunk 函数:
const exampleThunkFunction = (dispatch, getState) => {
const stateBefore = getState()
console.log(`Counter before: ${stateBefore.counter}`)
dispatch(increment()) // dispatch 普通的 action
const stateAfter = getState()
console.log(`Counter after: ${stateAfter.counter}`)
}

store.dispatch(exampleThunkFunction) // 将 thunk 函数 直接传递给 store.dispatch

为了与 dispatch 普通 action 对象保持一致,我们通常将它们写为 _thunk action creators_,它返回 thunk 函数。这些 action creator 可以接受可以在 thunk 中使用的参数。

1
2
3
4
5
6
7
8
9
10
11
const logAndAdd = amount => {
return (dispatch, getState) => {
const stateBefore = getState()
console.log(`Counter before: ${stateBefore.counter}`)
dispatch(incrementByAmount(amount))
const stateAfter = getState()
console.log(`Counter after: ${stateAfter.counter}`)
}
}

store.dispatch(logAndAdd(5))

Thunk 通常写在 “slice” 文件中。createSlice 本身对定义 thunk 没有任何特殊支持,因此你应该将它们作为单独的函数编写在同一个 slice 文件中。这样,他们就可以访问该 slice 的普通 action creator,并且很容易找到 thunk 的位置。

“thunk” 这个词是一个编程术语,意思是 “一段做延迟工作的代码”.

编写异步 Thunks

Thunk 内部可能有异步逻辑,例如 setTimeoutPromiseasync/await。这使它们成为使用 AJAX 发起 API 请求的好地方。

Redux 的数据请求逻辑通常遵循以下可预测的模式:

  • 在请求之前 dispatch 请求“开始”的 action,以指示请求正在进行中。这可用于跟踪加载状态以允许跳过重复请求或在 UI 中显示加载中提示。
  • 发出异步请求
  • 根据请求结果,异步逻辑 dispatch 包含结果数据的“成功” action 或包含错误详细信息的 “失败” action。在这两种情况下,reducer 逻辑都会清除加载状态,并且要么展示成功案例的结果数据,要么保存错误值并在需要的地方展示。

这些步骤不是 _必需的_,而是常用的。(如果你只关心一个成功的结果,你可以在请求完成时发送一个“成功” action ,并跳过“开始”和“失败” action 。)

Redux Toolkit 提供了一个 createAsyncThunk API 来实现这些 action 的创建和 dispatch,我们很快就会看看如何使用它。

细节说明

如果我们手动编写一个典型的 async thunk 的代码,它可能看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const getRepoDetailsStarted = () => ({
type: "repoDetails/fetchStarted",
})
const getRepoDetailsSuccess = repoDetails => ({
type: "repoDetails/fetchSucceeded",
payload: repoDetails,
})
const getRepoDetailsFailed = error => ({
type: "repoDetails/fetchFailed",
error,
})
const fetchIssuesCount = (org, repo) => async dispatch => {
dispatch(getRepoDetailsStarted())
try {
const repoDetails = await getRepoDetails(org, repo)
dispatch(getRepoDetailsSuccess(repoDetails))
} catch (err) {
dispatch(getRepoDetailsFailed(err.toString()))
}
}

提示:

Redux Toolkit 有一个新的 RTK Query data fetching API。 RTK Query 是专门为 Redux 应用程序构建的数据获取和缓存解决方案,可以不用编写任何 thunk 或 reducer 来处理数据获取

总结

可以编写可复用的“selector 选择器”函数来封装从 Redux 状态中读取数据的逻辑

  • 选择器是一种函数,它接收 Redux state 作为参数,并返回一些数据

Redux 使用叫做“ middleware ”这样的插件模式来开发异步逻辑

  • 官方的处理异步 middleware 叫 redux-thunk,包含在 Redux Toolkit 中
  • Thunk 函数接收 dispatchgetState 作为参数,并且可以在异步逻辑中使用它们

你可以 dispatch 其他 action 来帮助跟踪 API 调用的加载状态

  • 典型的模式是在调用之前 dispatch 一个 “pending” 的 action,然后是包含数据的 “sucdess” 或包含错误的 “failure” action
  • 加载状态通常应该使用枚举类型,如 'idle' | 'loading' | 'succeeded' | 'failed'

Redux Toolkit 有一个 createAsyncThunk API 可以为你 dispatch 这些 action

  • createAsyncThunk 接受一个 “payload creator” 回调函数,它应该返回一个 Promise,并自动生成 pending/fulfilled/rejected action 类型
  • fetchPosts 这样生成的 action creator 根据你返回的 Promise dispatch 这些 action
  • 可以使用 extraReducers 字段在 createSlice 中监听这些 action,并根据这些 action 更新 reducer 中的状态。
  • action creator 可用于自动填充 extraReducers 对象的键,以便切片知道要监听的 action。
  • Thunk 可以返回 promise。 具体对于createAsyncThunk,你可以await dispatch(someThunk()).unwrap()来处理组件级别的请求成功或失败。