React Tool Kit中乐观更新与创建项时UI状态区分问题
解决方案
要区分getItems重取的场景,核心思路是主动跟踪触发重取的操作类型,结合RTK Query的查询状态来控制UI行为,以下是两种可行的实现方式:
方式一:跟踪Create操作的状态(推荐)
针对createItem操作,在触发时标记当前处于创建流程,查询完成后清除标记,以此来决定是否显示加载状态。
代码示例
import { useState } from 'react'; import { useGetItemsQuery, useCreateItemMutation, useUpdateItemMutation } from './apiSlice'; function ItemList() { const [isCreating, setIsCreating] = useState(false); const { data: items, isFetching } = useGetItemsQuery(); // 创建项的Mutation const { mutate: createItem } = useCreateItemMutation({ onSuccess: () => { // 失效查询触发重取 useGetItemsQuery().invalidate(); }, onSettled: () => { // 无论成功/失败,结束后清除创建标记 setIsCreating(false); }, }); // 更新项的Mutation(带乐观更新) const { mutate: updateItem } = useUpdateItemMutation({ onMutate: async (updatedItem) => { // 暂停getItems查询,避免立即重取 await useGetItemsQuery().cancelQuery(); // 获取当前缓存数据 const previousItems = useGetItemsQuery().getState().data; // 乐观更新缓存 useGetItemsQuery().setData( undefined, previousItems.map(item => item.id === updatedItem.id ? { ...item, checked: updatedItem.checked } : item ) ); // 返回回滚数据 return { previousItems }; }, onError: (err, updatedItem, context) => { // 出错时回滚缓存 useGetItemsQuery().setData(undefined, context.previousItems); }, onSettled: () => { // 失效查询,同步最新数据 useGetItemsQuery().invalidate(); }, }); // 触发创建操作时标记状态 const handleCreateItem = (newItem) => { setIsCreating(true); createItem(newItem); }; // 判断是否需要显示加载/锁定UI const shouldLockUI = isCreating && isFetching; return ( <div> {shouldLockUI && ( <div className="spinner-overlay"> <div className="spinner">加载中...</div> </div> )} <button onClick={() => handleCreateItem({ name: '新项' })}>创建新项</button> <ul> {items?.map(item => ( <li key={item.id}> {item.name} <button onClick={() => updateItem({ id: item.id, checked: !item.checked })}> {item.checked ? '取消选中' : '选中'} </button> </li> ))} </ul> </div> ); }
逻辑说明
- 调用
createItem时先设置isCreating为true,此时若getItems开始重取(isFetching为true),则显示加载层锁定UI createItem的onSettled回调会在请求完成(无论成功失败)后清除标记,避免状态卡住- 对于
updateItem场景,因为已做乐观更新,UI已经处于正确状态,即使isFetching为true,也无需显示加载,用户感知不到后台的同步操作
方式二:利用缓存标记区分乐观更新状态
如果需要更精细化判断是否存在乐观更新的项,可以在乐观更新时给项添加临时标记,通过检查缓存数据中的标记来决定UI行为。
代码示例(核心部分)
// 在updateItem的乐观更新中添加临时标记 const { mutate: updateItem } = useUpdateItemMutation({ onMutate: async (updatedItem) => { await useGetItemsQuery().cancelQuery(); const previousItems = useGetItemsQuery().getState().data; useGetItemsQuery().setData( undefined, previousItems.map(item => item.id === updatedItem.id ? { ...item, checked: updatedItem.checked, _isOptimistic: true } : item ) ); return { previousItems }; }, onError: (err, updatedItem, context) => { useGetItemsQuery().setData(undefined, context.previousItems); }, onSettled: async (result, err, updatedItem) => { await useGetItemsQuery().invalidate(); // 重取完成后清除所有乐观标记 const currentData = useGetItemsQuery().getState().data; if (currentData) { useGetItemsQuery().setData( undefined, currentData.map(item => ({ ...item, _isOptimistic: undefined })) ); } }, }); // 判断是否需要显示加载 const hasOptimisticItems = items?.some(item => item._isOptimistic); const shouldShowLoading = isFetching && !hasOptimisticItems && isCreating;
逻辑说明
- 乐观更新时给修改的项添加
_isOptimistic临时标记 - 当
getItems重取时,如果存在带标记的项,说明是乐观更新后的同步,不需要显示加载 - 结合
isCreating标记,可以同时覆盖两种场景的判断
内容的提问来源于stack exchange,提问作者PBrown
相关产品推荐
相关产品推荐

