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

RTK Query中仅更新单个用户时Optimistic updates失效求助

问题描述

基于RTK Query搭建CRUD接口实现乐观更新时,调用updateQueryData更新getSingleUser缓存时,onQueryStarted中的draft函数完全不执行;但更新getAllUsers缓存时乐观更新正常工作,期望实现单个用户更新时的乐观更新。

代码示例

原问题代码

import { EntityState, createEntityAdapter } from '@reduxjs/toolkit'
import api from '../api'

export type User = {
    userId: number
    name: string
    username: string
}

const usersAdaptor = createEntityAdapter<User>({
    selectId: (model) => model.userId,
})

const initialState = usersAdaptor.getInitialState()

const extendedUsersAPI = api.injectEndpoints({
    endpoints: (builder) => ({
        getAllUsers: builder.query<EntityState<User>, undefined | void>({
            query: () => ({ url: '/users', method: 'GET' }),
            transformResponse: (result: User[]) =>
                usersAdaptor.setAll(initialState, result),

            providesTags: (result?: EntityState<User>) =>
                result
                    ? [
                          { type: 'Users', id: 'LIST' },
                          ...result.ids.map((id) => ({
                              type: 'Users' as const,
                              id,
                          })),
                      ]
                    : [{ type: 'Users', id: 'LIST' }],
        }),

        getSingleUser: builder.query<EntityState<User>, number>({
            query: (userId) => ({ url: '/users/' + userId, method: 'GET' }),

            transformResponse: (result: User) =>
                usersAdaptor.setOne(initialState, result),
            providesTags: (_result, _meta, id) => [{ type: 'Users', id }],
        }),
        updateUser: builder.mutation<void, User>({
            query: (user) => ({
                url: `/users/${user.userId}`,
                method: 'PUT',
                data: user,
            }),

            async onQueryStarted(
                { userId, ...patch },
                { dispatch, queryFulfilled }
            ) {
                const patchResult = dispatch(
                    extendedUsersAPI.util.updateQueryData(
                        'getSingleUser',
                        userId,
                        (draft) => {
                            console.log('sc') // <------------ never runs!
                            Object.assign(draft, patch)
                        }
                    )
                )
                try {
                    await queryFulfilled
                } catch {
                    patchResult.undo()
                }
            },
        }),
        addNewUser: builder.mutation<EntityState<User>, Partial<User>>({
            query: (user) => ({ url: '/users', method: 'POST', data: user }),

            invalidatesTags: (_result, _error, arg) => [
                { type: 'Users', id: arg.userId },
            ],
        }),
    }),
})

export const {
    useGetAllUsersQuery,
    useLazyGetAllUsersQuery,
    useAddNewUserMutation,
    useUpdateUserMutation,
} = extendedUsersAPI

可正常运行的修改(更新getAllUsers)

{
// ...
updateUser: builder.mutation<void, User>({
         query: (user) => ({
            url: `/users/${user.userId}`,
            method: 'PUT',
            data: user,
         }),

         async onQueryStarted(
            { userId, ...patch },
            { dispatch, queryFulfilled }
         ) {
            const patchResult = dispatch(
               extendedUsersAPI.util.updateQueryData(
                  'getAllUsers',
                  undefined,
                  (draft) => {
                     console.log('sc') // <------------ this runs!
                     Object.assign(draft, patch)
                  }
               )
            )
            try {
               await queryFulfilled
            } catch {
               patchResult.undo()
            }
         },
      }),
}
问题原因
  1. 缓存不存在:updateQueryData仅会在目标查询(这里是getSingleUser)已经被执行过且结果存在于缓存中时,才会调用draft函数。如果当前页面没有先调用getSingleUser获取对应userId的数据,缓存中没有该查询的条目,draft函数自然不会执行。
  2. 更新方式错误:即使缓存存在,原代码中直接Object.assign(draft, patch)是错误的——因为draft是EntityState类型(包含ids和entities),而非单个User对象,直接赋值无法更新到正确的实体数据。
解决方案

步骤1:确保getSingleUser缓存存在

在执行updateUser之前,确保已经通过useGetSingleUserQuery或useLazyGetSingleUserQuery获取过目标用户的数据,让其结果存入缓存。

步骤2:正确更新EntityState中的用户数据

使用usersAdaptor提供的updateOne方法来修改draft中的实体,而不是直接操作draft对象:

updateUser: builder.mutation<void, User>({
    query: (user) => ({
        url: `/users/${user.userId}`,
        method: 'PUT',
        data: user,
    }),

    async onQueryStarted(
        { userId, ...patch },
        { dispatch, queryFulfilled }
    ) {
        const patchResult = dispatch(
            extendedUsersAPI.util.updateQueryData(
                'getSingleUser',
                userId,
                (draft) => {
                    console.log('sc') // 现在会执行了
                    // 使用adaptor的updateOne方法更新实体
                    usersAdaptor.updateOne(draft, {
                        id: userId,
                        changes: patch
                    })
                }
            )
        )
        try {
            await queryFulfilled
        } catch {
            patchResult.undo()
        }
    },
}),

可选:强制缓存预加载(如果需要)

如果无法保证getSingleUser已被调用,可以在updateUser的onQueryStarted中先触发getSingleUser的查询(使用dispatch(extendedUsersAPI.endpoints.getSingleUser.initiate(userId))),确保缓存存在后再执行更新,但这种方式可能会额外发起请求,需根据业务场景权衡。

内容的提问来源于stack exchange,提问作者Reza

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 01:40:55