Redxu(RTK) 基础 异步逻辑与数据请求 第5.1节 Thunks 与异步逻辑e

Thunks 与异步逻辑

额,之前已经在快速上手里边接触过thunk,反正异步的东西不能放进reducer,所以用thunk替代,好像顺理成章的thunk就是异步版本的reducer? 这样说不准确,其实thunk是一种redux中间件,他插入在了dispatch和reducer之间的位置。

(用于处理thunk的)Redux middleware 在 dispatch action 和到达 reducer 之间提供第三方扩展点

这个Redux 中间件不是什么可怕的东西,其内部范式我不去深究,但是它的作用和Express中的中间件一样,起一个拦截、做点修改的作用。
如果需要详细了解,
这里介绍了Redux 中间件
这里介绍了thunk 中间件

Redux store对异步逻辑一无所知,他只知道如何同步dispatch action,并通过调用root reducer函数来更新状态(是所有slice组成的东西),并告知已经订阅store的UI发生了变化。 任何异步过程必须发生在store以外。

上面就是说,哎呀,store只处理同步的action,异步的action人家不管

但是,如果你希望通过调度或检查当前 store 状态来使异步逻辑与 store 交互,该怎么办? 这就是 Redux middleware 的用武之地。它们扩展了 store

上面是说,如果要和store做异步交互,需要一个扩展,这个扩展在Redux里边叫做 Redux middleware,功效如下:

redux中间件允许你:

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

从上面四点,我看到了亮点,

  • 很明显,在中间件里边,能获取对dispatch和getState的引用
  • 中间件里可以处理promise等异步的东西,并把结果变成纯对象,也就是action,来和store正常交互(因为中间件就是函数吖)

额,中间件最常用与处理不同类型的异步逻辑(的结果)与store交互。

Redux(社区玩的花吖)有多种异步中间件,各有千秋,最常见的是redux-thunk,它可以让你编写可能直接包含异步逻辑的普通函数,RTK的configureStore功能,默认自动设置 thunk middleware我们推荐使用 thunk 作为 Redux 开发异步逻辑的标准方式。 (我看了,人家默认有三个中间件,一个赛一个狠,太强了)

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

上面的gif动图在store上面插入了一个中间件,很明显,中间件拦截了Dispatch发出的内容,判断这个内容是action还是thunk,如果是action,那直接交给stotre,如果是thunk,那么异步操作的事情由他处理,自己处理完了,再把纯action交付给store。
也就是说,默认情况下redux只能处理同步逻辑,action被dispatch后,直接发送给store。这个处理thunk的中间件给redux扩展了一个功能,解决了dispatch之后,store处理之前的阶段的问题,在这一阶段,处理异步逻辑。
要注意一点,thunk是个以dispatch和getState作为参数的函数,正因为能获取这两个参数,就可以在thunk内部决定合适的时机dispatch一个action,所以thunk可以处理异步逻辑,又能在异步过程结束后发起改变store的“请求”。
另外,thunk和处理thunk的中间件不是一个东西,thunk中间件让redux能识别thunk并能够让thunk发挥作用。具体见这里编写异步函数 Middleware
ReduxToolkit已经默认开启了该中间件,所以不用再引入对中间件的依赖和配置该中间件了(像以前一样引入react-redux)。

Thunk函数

将 thunk middleware 添加到 Redux store 后,它允许你将 thunk 函数 直接传递给 store.dispatch。调用 thunk 函数时总是将 (dispatch, getState) 作为它的参数,你可以根据需要在 thunk 中使用它们。(如果忘了,可以翻一翻之前的关于thunk的补充)
Thunks 通常还可以使用 action creator 再次 dispatch 普通的 action,比如 dispatch(increment()):

const store = configureStore({ reducer: counterReducer })

const exampleThunkFunction = (dispatch, getState) => {
  const stateBefore = getState()
  console.log(`Counter before: ${stateBefore.counter}`)
  dispatch(increment())
  const stateAfter = getState()
  console.log(`Counter after: ${stateAfter.counter}`)
}

store.dispatch(exampleThunkFunction)

上面的就是简化后的程序,创建了store,并手写一个thunk,可见thunk接受了dispatch和getState,所以可以dispatch普通action(比如在异步过程出结果后把数据包装进action再dispatch),那么在这里到底能不能更新store的数据呢?我的建议是不能捏!
为了与 dispatch 普通 action 对象保持一致,我们通常将它们写为 thunk action creators,它返回 thunk 函数。这些 action creator 可以接受可以在 thunk 中使用的参数。

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,他们真的很复杂,我墙裂建议你使用RTK提供的createAsyncThunk捏。
一方面,不用再考虑那么多函数套函数和多个参数,另外一方面,不用手动dispatch,全自动包装返回值为action,当然,这也是有代价的,代价就是你要写一份冗长但是机械化的extraReducers,其中规定了异步过程三个状态(pending,fulfilled,rejected)对应的状态变化。

编写异步 Thunks

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

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

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

额,上面说的内容对应 extraReducers,而extraReducers你还是得自己去看看前面教程,不要怕,很简单的辣。

教程反复推荐createAsyncThunk,如果你非要头铁手写,下面的是手写async thunk 代码的大概样子捏:

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()))
  }
}

但是,使用这种方法编写代码很乏味。每个单独的请求类型都需要重复类似的实现:
需要为三种不同的情况定义独特的 action 类型
每种 action 类型通常都有相应的 action creator 功能
必须编写一个 thunk 以正确的顺序发送正确的 action
createAsyncThunk 实现了这套模式:通过生成 action type 和 action creator 并生成一个自动 dispatch 这些 action 的 thunk。你提供一个回调函数来进行异步调用,并把结果数据返回成 Promise。

那么,如何使用createAsyncThunk捏?

我们直接看模板生成的示例!

// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched. Thunks are
// typically used to make async requests.
export const incrementAsync = createAsyncThunk(
  "counter/fetchCount",
  async (amount: number,thunkAPI) => {
    const response = await fetchCount(amount);
    // The value we return becomes the `fulfilled` action payload
    return response.data;
  }
);

可见,他接受两个参数,第一个确定该thunk发出的action的名称,第二个则是用于处理异步逻辑的函数,这样,不用我们手动去dispatch一个action,该函数的返回值会自动被包装进action并触发。
而如果你还想手动触发action,或者想获取当前store的状态,请使用thunkAPI,很明显,其他更强大的功能也是在thunkAPI。

当然不要忘记,async thunk 一般配合 extraReducers使用。

export const counterSlice = createSlice({
  name: "counter",
  initialState,
  // The `reducers` field lets us define reducers and generate associated actions
  reducers: {//省略其他代码 }
  // The `extraReducers` field lets the slice handle actions defined elsewhere,
  // including actions generated by createAsyncThunk or in other slices.
  extraReducers: (builder) => {
    builder
      .addCase(incrementAsync.pending, (state: any) => {
        state.status = "loading";
      })
      .addCase(incrementAsync.fulfilled, (state: any, action: any) => {
        state.status = "idle";
        state.value += action.payload;
      })
      .addCase(incrementAsync.rejected, (state: any) => {
        state.status = "failed";
      });
  },
});
絮叨的总结 thunk,thunk中间件,thunk的使用

总觉得上面内容虽多,但有些杂乱无章,让我有种隔靴搔痒的感觉,所以在这里做个总结。
1.thunk就是个普通函数,接受dispatch和getState两个参数,可以用于在thunk内部发起action和获取store数据
2.没有thunk中间件,redux不认识thunk函数,有了thunk中间件,thunk函数可以直接被dispatch(像action一样),由中间件判断具体是action还是thunk被dispatch,
如果是action,那继续交给store中的reducer处理,如果是thunk,那运行thunk,然后thunk会包装一个action并dispatch,继续走正常action的流程。
3.不要在reducer内部编写异步逻辑,交给thunk处理异步逻辑!
4.为了和dipsatch 一个action对象的形式保持一致,我们一般对thunk做一个包装处理,用函数包装thunk,该函数仅仅是返回一个thunk函数,这样调用包装后的thunk函数,效果和直接dispatch 一个thunk函数没什么区别,但形式上看上去和dispatch一个action无异。

posted @ 2023-03-03 20:45  刘老六  阅读(124)  评论(0编辑  收藏  举报