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无异。