You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 05:07:43