ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Redux 异步副作用逻辑怎么写:thunk 和 saga 怎么选?

Redux 异步副作用逻辑怎么写:thunk 和 saga 怎么选? Redux 异步副作用逻辑怎么写thunk 和 saga 怎么选【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux在 Redux 应用里写异步逻辑发请求、按结果再 dispatch action、需要读取 store state 做多步判断时最常见的两个选择是 thunk 和 saga。这篇文章基于 Redux 官方文档给出一条可执行的路径先按使用场景判断该用哪个然后分别展示两种写法的完整代码并说明如何在 Redux DevTools 中验证 action 确实被派发。适用前提是你已经有 Redux store现代写法一般使用 Redux Toolkit 的configureStore创建。先按场景选工具thunk 和 saga 的分工Redux 官方文档FAQ: Actions 中 What async middleware should I use? 一节给出的选型规则很明确Thunk最适合复杂同步逻辑尤其是需要访问整个 Redux store state 的代码以及简单到中等复杂度的异步逻辑如一次标准 AJAX 请求。配合async/await时部分更复杂的 Promise 逻辑也可以用 thunk 写。Saga最适合复杂异步逻辑和需要后台线程式行为的场景尤其是需要监听已 dispatch 的 action 再触发额外逻辑——这是 thunk 做不到的。前提是你能接受 generator function 语法和redux-saga的 effects 运算符。文档同时建议大多数 Redux 用户应该先从 thunk 开始只有当应用确实需要处理更复杂的异步逻辑时再引入 saga 或 observables。另外thunk 与 saga或 observable解决的是不同问题两者完全可以共存于同一个应用而 saga 和 observable 用途相同一般二选一不同时使用。Side Effects Approaches 还补充了一条按任务类型的官方推荐写作异步代码前可以先对照你的任务官方推荐数据获取与缓存最常用场景默认用 RTK Query不合适时用createAsyncThunk再退一步才手写 thunk。明确不建议用 saga 做数据获取响应 action / state 变化的reactive逻辑、长时异步工作流默认用 RTK listener middleware只有它解决不了时才考虑 saga需要访问getState、连续 dispatch 多个 action 的逻辑thunk也就是说如果只是想发一次请求、把结果放进 store文档推荐的默认路径是 RTK Query 或createAsyncThunk而不是 saga只有逻辑复杂到需要监听其他 action、取消/防抖、子任务并行时才轮到 saga。用 thunk 写异步逻辑1. 准备安装并挂载 thunk 中间件如果 store 是用 Redux Toolkit 的configureStore创建的thunk 中间件会在创建 store 时自动加入通常不需要额外配置见 Writing Logic with Thunks。如果直接用 Redux 核心的createStore需要先安装中间件包并手动挂到 store 上示例来自 Fundamentals Part 6npm install redux-thunkimport { createStore, applyMiddleware } from redux import { thunk } from redux-thunk import { composeWithDevTools } from redux-devtools-extension import rootReducer from ./reducer const composedEnhancer composeWithDevTools(applyMiddleware(thunk)) // store 现在可以接受 thunk 函数传入 dispatch const store createStore(rootReducer, composedEnhancer) export default store2. 写 thunk 函数和 thunk action creatorThunk 函数是一个接受dispatch和getState两个参数的函数里面可以写任意同步或异步逻辑。Thunk 函数不由应用代码直接调用而是传给store.dispatch()。实际项目中通常用thunk action creator外层同步函数接收参数、返回 thunk 函数让参数在逻辑中可用// 外层函数接收参数这里来自 Fundamentals 教程的 todo 示例 export function saveNewTodo(text) { // 返回真正的 thunk 函数 return async function saveNewTodoThunk(dispatch, getState) { const initialTodo { text } const response await client.post(/fakeApi/todos, { todo: initialTodo }) dispatch({ type: todos/todoAdded, payload: response.todo }) } }上面的client和/fakeApi端点都来自教程配套的内存假 API教程项目中定义在src/api/client.js和src/api/server.js实际项目里替换成你自己的 HTTP client 和接口地址即可。然后在组件里通过useDispatch派发调用方式和派发普通 action 一样import { saveNewTodo } from ../todos/todosSlice const Header () { const [text, setText] useState() const dispatch useDispatch() const handleKeyDown e { const trimmedText text.trim() if (e.which 13 trimmedText) { // 调用 action creator 生成 thunk 函数并立即传给 dispatch dispatch(saveNewTodo(trimmedText)) setText() } } // omit rendering output }如果 thunk 里有异步逻辑还可以await它的返回值thunk 中间件会返回 thunk 函数的返回值最常见的是返回 Promise用来在派发方协调后续工作const onAddTodoClicked async () { await dispatch(saveTodo(todoText)) setTodoText() }3. thunk 的能力边界写逻辑前先确认它适不适合放 thunk。官方文档列出的常见用途把复杂逻辑移出组件、发异步请求、连续/分次 dispatch 多个 action、用getState做条件判断或给 action 补充数据。注意两点限制Thunk 是one-shot函数没有生命周期概念看不到其他被 dispatch 的 action所以不能用来响应其他 action也不适合用来初始化 websocket 这类持久连接这类逻辑官方建议放在自定义中间件里。如果请求失败thunk 的catch块写法要小心避免把处理成功结果过程中的错误误判为网络错误——文档建议失败时先 dispatch rejected 然后提前return让 fulfilled 只出现在逻辑末尾。标准请求并发 action用 createAsyncThunk如果异步逻辑就是请求前后各 dispatch 一个 action这种最常见形态手写的三件套pending/fulfilled/rejected 三个 action type 对应 creator thunk 本身比较啰嗦。Redux Toolkit 的createAsyncThunk会生成这些 action 并按 Promise 生命周期自动派发见 Fundamentals Part 8import { createSlice, createAsyncThunk } from reduxjs/toolkit // 第二个参数是 payload creator返回 Promise export const fetchTodos createAsyncThunk(todos/fetchTodos, async () { const response await client.get(/fakeApi/todos) return response.todos }) const todosSlice createSlice({ name: todos, initialState, reducers: { // omit reducer cases }, // thunk 的 action 定义在 createSlice 之外用 extraReducers 监听 extraReducers: builder { builder .addCase(fetchTodos.pending, (state, action) { state.status loading }) .addCase(fetchTodos.fulfilled, (state, action) { state.entities action.payload state.status idle }) } })createAsyncThunk接收 action type 前缀字符串和 payload creator自动派生三个 action typefetchTodos.pendingtodos/fetchTodos/pendingfetchTodos.fulfilledtodos/fetchTodos/fulfilledfetchTodos.rejectedtodos/fetchTodos/rejected运行时行为先 dispatchpending再执行 payload creator最后根据返回的 Promise 成功与否 dispatchfulfilled或rejected。两点使用限制dispatch 时只能传一个参数需要多个值就包成一个对象它只覆盖异步请求这一种用例需要写同步逻辑或自定义行为时仍要手写普通 thunk。什么情况下选 sagaworker / watcher 写法当逻辑需要监听某个 action被触发后运行一段可以暂停/取消的后台流程时才是 saga 的目标场景。Saga 用 generator 函数编写saga 函数yield的其实是副作用的描述由 saga 中间件负责执行并把结果传回。redux-saga常用 effects 包括摘自 Side Effects Approachescall执行异步函数Promise 完成后返回结果putdispatch 一个 Redux actionfork派生子 saga类似额外线程takeLatest监听某个 action再次派发时取消上一次仍在运行的副本典型的 worker / watcher 结构import { call, put, takeEvery } from redux-saga/effects // Worker saga每次派发 USER_FETCH_REQUESTED 时运行 function* fetchUser(action) { yield put(fetchUserStarted()) try { const user yield call(userApi.getUserById, action.payload.userId) yield put(fetchUserSucceeded(user)) } catch (err) { yield put(fetchUserFailed(err.message)) } } // Watcher saga监听 action 并启动 worker function* fetchUserWatcher() { yield takeEvery(USER_FETCH_REQUESTED, fetchUser) }文档同时给出了 saga 的 tradeoff选型时应当对照优点saga 只返回副作用描述而非直接执行可测试性好effects 模型能力强支持暂停/取消。缺点generator 函数语法复杂有独立的 effects API 需要学习saga 测试往往只在测实现细节每次改动 saga 都要重写测试TypeScript 支持不好。还有一条明确的官方立场Style Guide 建议不要在大多数场景尤其是异步数据获取使用 Redux-Saga 和 Redux-Observable只有当其他工具能力都不够时才引入它们。数据获取场景用 saga 时缓存和 loading 状态逻辑仍需自己全部实现官方认为这抵消了 saga 带来的能力。验证结果thunk 路径的验证方式文档中是明确的派发后打开 Redux DevTools 扩展应该能看到对应 action 出现在历史中且 payload 是接口返回的数据。Fundamentals 教程里的验证点就是执行store.dispatch(fetchTodos)后DevTools 中出现todos/todosLoadedaction内容包含假服务端生成的 todo 对象对应截图website/static/img/tutorials/fundamentals/devtools-todosLoaded-action.png。如果 reducer 还没有处理该 action type状态不会更新——教程特别提示即使看到 action 被 dispatch也必须先给 reducer 加对应 case 才会改 state所以验证时要区分action 已派发和state 已更新两件事。用createAsyncThunk时可以按生成的pending/fulfilled/rejected三个 action type 去 DevTools 中核对请求开始时出现…/pendingPromise 成功或失败后分别出现…/fulfilled或…/rejected。saga 部分文档没有给出对应的 DevTools 验证步骤以上验证仅适用于 thunk 与createAsyncThunk路径。小结按这张表落到具体写法场景选择复杂同步逻辑、需要getState做判断、连续派发多个 action手写 thunk一次请求 按结果更新 storecreateAsyncThunk或默认用 RTK Query响应其他 action、后台线程式工作流、取消/防抖、子任务listener middleware 优先确实不够再上 saga数据获取与缓存RTK Query明确不建议 sagawebsocket 等持久连接自定义中间件不是 thunk 也不是 saga继续深入可以看 Writing Logic with Thunks、Side Effects Approaches 和 Fundamentals Part 6: Async Logic and Data Fetching 的完整示例。【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表