React-Redux中如何顺序调用Action并确保均完成渲染?
我现在遇到一个Redux-Thunk的异步流程问题:需要先完成loadAll Action的状态更新和组件渲染,再执行toggleAddItem关闭弹窗。但目前的代码里,toggleAddItem总是在loadAll的渲染完成前就触发,甚至先完成,偶尔会出现弹窗关闭了但新列表没渲染的情况。
我的现有代码如下:
export const loadAllItems = items => ({ type: LOAD_ALL_ITEMS, payload: items }); export const toggleAddItem = status => ({ type: TOGGLE_ADD_ITEM, payload: status }) export const loadAll = () => (dispatch => { axios.get('/getAllItems').then(res => { dispatch(loadAllItems(res.data)) }) .catch(err => { console.log(err); }) }); export const addItemAndRefresh = input => ((dispatch, getState) => { axios.post('/addItem', input) .then(() => { axios.get('/getAllItems') .then(res => { dispatch(loadAll(res.data)) }) .then(() => { dispatch(toggleAddItem(false)); }) }) .catch(err => { console.log(err); }) });
问题根源
你当前的代码里,dispatch(loadAll(res.data))之后直接链式调用.then(() => dispatch(toggleAddItem(false))),但loadAll本身是一个返回函数的thunk,dispatch(loadAll(...))并不会返回Promise,所以这个.then会立刻执行,根本等不及loadAll里的异步请求和状态更新完成——这就是弹窗先关闭、列表渲染没跟上的核心原因。
解决方案
1. 让loadAll返回Promise
首先修改loadAll,让它返回axios请求的Promise,这样我们就能在它完成后再执行后续操作:
export const loadAll = () => (dispatch => { // 返回axios的Promise,让外层可以等待请求和dispatch完成 return axios.get('/getAllItems').then(res => { dispatch(loadAllItems(res.data)) }) .catch(err => { console.log(err); // 抛出错误让外层catch统一处理 throw err; }) });
2. 修正addItemAndRefresh的链式逻辑
现在dispatch(loadAll())会返回Promise,我们可以正确等待它完成后再关闭弹窗:
export const addItemAndRefresh = input => ((dispatch, getState) => { axios.post('/addItem', input) .then(() => { // 等待loadAll的异步请求和dispatch全部完成 return dispatch(loadAll()); }) .then(() => { // 此时可以确定loadAll的状态更新已完成,再触发关闭弹窗 dispatch(toggleAddItem(false)); }) .catch(err => { console.log(err); }) });
3. 组件层面兜底(可选)
如果还是偶尔出现渲染不同步的问题,可以在组件里通过生命周期/副作用钩子,监听列表状态的变化来触发关闭弹窗:
类组件(componentDidUpdate)
componentDidUpdate(prevProps) { // 当items列表更新,且弹窗处于打开状态时,关闭弹窗 if (prevProps.items !== this.props.items && this.props.isAddItemOpen) { this.props.toggleAddItem(false); } }
函数组件(useEffect)
useEffect(() => { if (isAddItemOpen) { toggleAddItem(false); } }, [items, isAddItemOpen, toggleAddItem]);
这种方式相当于把“关闭弹窗”的时机交给组件的渲染完成事件,能彻底避免状态更新和渲染的时序问题。
总结
核心是让异步thunk Action返回Promise,在Redux层面保证执行顺序;如果还存在渲染时机问题,再结合组件的生命周期/副作用钩子监听状态变化,就能彻底解决弹窗先关、列表未渲染的问题。
内容的提问来源于stack exchange,提问作者lehoang

