如何使用 redux-saga 处理异步 API 调用?
Posted
技术标签:
【中文标题】如何使用 redux-saga 处理异步 API 调用?【英文标题】:How to handle async API calls using redux-saga? 【发布时间】:2021-06-18 14:16:46 【问题描述】:最近,我们开始使用 redux-saga 中间件来管理来自 store 的异步 API 调用。
以前,我们使用基于承诺的通用操作,一旦我们可以在操作本身上使用 async/await
和 .then()
和 .catch()
,就可以轻松管理组件流。
承诺:
const fetchCars = async () =>
const data = await fetchCarsRequest();
setCars(data);
cars.map((car) => <li>car.name</li>);
与传奇:
function* fetchCarsSaga()
try
const data = yield call(fetchCarsService);
yield put(fetchCarsSuccess);
catch(error)
yield put(fetchCarsFailure);
const fetchCars = async () =>
fetchCarsRequest(); //action that calls the above saga
setCars(cars); //state from store
cars.map((car) => <li>car.name</li>);
在第二个例子中,即使我们使用生成器来处理异步API调用,也不可能使用await
语句,所以它在调用fetchCarsRequest
之后立即调用setCars
函数,尝试将尚不存在的东西设置为组件状态。
所以我的问题是:有没有办法将 await
语句替换为某些东西并等待请求函数(不是异步函数)完成 API 调用?
【问题讨论】:
不清楚你在问什么 - 你使用的名称与代码中的函数/生成器名称不完全匹配这一事实无济于事。但是 redux-saga 的call
会自动“等待”一个 Promise 来解决,如果被调用的函数返回一个 Promise - 就像所有的 async
函数一样。如果您将await
放在对fetchCarsRequest
的调用之前(因此该函数隐式返回的承诺直到fetchCarsRequest
才解决)那么我认为一切都应该正常吗?
我想我表达得不够好。我的主要疑问是,一旦 saga 处理异步调用,如何等待 API 调用完成调用另一个函数而不使用 await
。例如,我想等待 saga 完成 API 调用并将状态设置为 store,以打开成功模式。我该怎么做?
【参考方案1】:
使用 redux-saga,流程会有些不同。您不直接在组件中使用生成器。您需要从 saga 更新 redux 状态并在组件中获取它。
react component -> redux action -> saga -> redux state -> react component
您可以看看 PT-BR 中的两篇文章:
https://medium.com/nossa-coletividad/redux-saga-voc%C3%AA-no-controle-das-opera%C3%A7%C3%B5es-ass%C3%ADncronas-71c9e6b3aabc https://oieduardorabelo.medium.com/redux-saga-gerenciando-efeitos-f518a31c744e一个好的create-react-app
模板是:
在 CodeSandbox 中:
https://codesandbox.io/s/6mgb2【讨论】:
感谢@oieduardorabelo 的回复。文章真的很有用。但我的问题是:例如,我如何等待 saga 完成 API 调用并将状态设置为商店,然后才打开成功模式?在这种情况下,调用 saga 的操作将在单击按钮时被调用。 @GabrielSantana,您需要在 redux 状态下控制它。假设你的 saga 用yield put( type: "CAR_MODAL_OPEN" )
更新了 redux 状态,这个动作触发了一个返回的 reducer 和 modals: car: "open"
的新状态,然后在你的 react 组件中,你可以将此状态映射到 props,并在你检查的渲染函数中props.carModal === 'open'
渲染你需要的任何东西。循环react component -> redux action -> saga -> redux state -> react component
是状态流动/更新的方式。
是的。事实上,它确实解决了问题。但它仍然在减速器内部处理,具有全局状态,而不是在调用减速器的组件内部。也许没有办法像await
那样等待。我想对于 sagas,你总是必须跟踪 edux 状态的变化,才能根据状态值在 useEffect
内做一些事情。
你是绝对正确的。您需要先同步 redux-saga 和 redux,然后将 redux 与 react 组件同步,然后再将 props 与 useEffect
同步...不是很好,也不是很糟糕!哈哈哈祝@GabrielSantana 好运!【参考方案2】:
杀死组件状态
您想要调度一个动作fetchCarsRequest()
,它会触发一个 API 调用并将结果保存到您的 redux 存储中。你想等待这个完成,然后调用setCars(cars)
将你的组件状态设置为来自 redux 状态的值。为什么?
这是一种反模式。数据已经存在于 redux 中,应该直接从组件中的 redux 访问。您应该使用useSelector
或connect
HOC 从redux 中选择cars
的值。一旦您的 fetch 发送 fetchCarsSuccess
并更新了存储状态,它将自动使用新数据重新渲染您的组件。
它应该看起来像这样。
传奇
function* fetchCarsSaga()
try
const data = yield call(fetchCarsService);
// this action should store the data to the store
// it needs the data as an argument!
yield put(fetchCarsSuccess(data));
catch(error)
yield put(fetchCarsFailure(error));
组件
const Cars = () =>
const dispatch = useDispatch();
const cars = useSelector( state => state.cars.data ) || [];
const isLoading = useSelector( state => state.cars.isLoading );
const error = useSelector( state => state.cars.error );
useEffect( () =>
dispatch(fetchCarsRequest()); // action that calls the saga
, [] );
return (
!! error && (<div>Error: error</div>)
isLoading ? <Spinner/> : (
<ul>
cars.map((car) => <li key=car.id>car.name</li>)
</ul>
)
)
【讨论】:
@Gabriel 在我看来,useEffect
是“正确”的方式,所以我不确定你为什么要这样做。 redux-thunk 中间件使您可以await
dispatch
。我不认为 redux-saga 会这样做,但我从未尝试过。
你们完全正确。它确实解决了useEffect
的问题,但仅限于这种情况。事实上,我几乎在每个必须在一开始就发出 API 请求的组件中都这样做,我的问题是:我如何等待 saga 完成 API 调用并将状态设置为商店,然后才打开例如,成功模式?在这种情况下,将在单击按钮时调用请求。以上是关于如何使用 redux-saga 处理异步 API 调用?的主要内容,如果未能解决你的问题,请参考以下文章
如何防止 redux-saga 调用我的 api 调用两次?