只要我从服务器获得正确的数据,手动更新缓存是否应该始终是突变后的首选?

Should manually updating the cache always be the preferred option after mutations as long as I get proper data from server?

我正在使用 React Query 编写一个 CRUD 应用程序,并且我创建了一些自定义挂钩,如下所述:https://react-query.tanstack.com/examples/custom-hooks

在文档中我看到基本上有两种方法可以在突变后更新缓存:

onSuccess: () => {
  queryClient.invalidateQueries("posts");
}
// Update post example

// I get the updated post data for onSuccess
onSuccess: (data) => {
  queryClient.setQueryData("posts", (oldData) => {
    const index = oldData.findIndex((post) => post.id === data.id);

    if (index > -1) {
      return [
        ...oldData.slice(0, index),
        data,
        ...oldData.slice(index + 1),
      ];
    }
  });
},

我知道手动更新的优点是不需要额外调用来获取 'posts',但我想知道使缓存无效是否比手动更新有任何优势。例如:

import { useMutation, useQueryClient } from "react-query";
const { API_URL } = process.env;

const createPost = async (payload) => {
  const options = {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  };

  if (API_URL) {
    try {
      const response = await fetch(API_URL, options);

      if (!response.ok) {
        throw new Error(response.statusText);
      }
      return response.json();
    } catch (error) {
      throw new Error(error);
    }
  } else {
    throw new Error("No api url is set");
  }
};

export default function useCreatePost() {
  const queryClient = useQueryClient();

  return useMutation((payload) => createPost(payload), {
    // DOES INVALIDATING HAVE ANY ADVANTAGE OVER MANUAL UPDATE IN THIS CASE?
    // onSuccess: () => {
    //   queryClient.invalidateQueries("posts");
    // },
    onSuccess: (data) => {
      queryClient.setQueryData("posts", (oldData) => {
        return [...oldData, data];
      });
    },
  });
}

感谢您的宝贵时间!

正如您自己所说,唯一的好处是您不会浪费另一个网络调用来更新我们已有的数据

这里我们有一个创建和删除的例子。

 import { useMutation, useQueryClient } from 'react-query'
 
 const queryClient = useQueryClient()

 // createPost(post: PostT) {
 //   const { data } = await http.post<{ post: PostT >('/posts', { post });
 //   return data.post;
 // }

 const mutation = useMutation(createPost, {
   onSuccess: (post) => {
     queryClient.setQueryData<PostT[]>(['posts'], (oldData || []) => [ ...oldData, post])
   },
 })

 // deletePost(id: string) {
 //   await http.delete(`/posts/${id}`);
 // }

 const mutation = useMutation(deletePost, {
   onSuccess: (_, id) => {
     queryClient.setQueryData<PostT[]>(['posts'], (oldData || []) => oldData.filter((post) => id !== post.id)
   },
 })

在某些情况下,使查询无效也是一种选择。查询将失效,数据将被标记为过时。这将在后台触发重新获取。所以你知道数据将尽可能新鲜。

如果你有:

  • 使用突变数据更新的多个查询
  • 有一个(困难的)嵌套数据结构要更新
 import { useMutation, useQueryClient } from 'react-query'
 
 const queryClient = useQueryClient()

 const mutation = useMutation(createPost, {
   onSuccess: () => {
     queryClient.invalidateQueries('posts')
     queryClient.invalidateQueries('meta')
     queryClient.invalidateQueries('headers')
   },
 })

但这真的取决于你。

使用手动更新的主要优势在于您可以在将请求发送到服务器之前完成;因此,如果您在请求成功后手动更新,那么如果您从服务器获得的数据不需要立即呈现给用户并且在这些情况下(我发现这是大多数)你最好还是无效。当您使用乐观更新时,您假设请求成功,然后再将其发送到服务器,然后如果请求失败,您只需回滚您的更新。这样你的动作就会立即发生,这比执行动作、显示加载微调器或其他东西然后显示更新状态更好的用户体验。所以我发现向用户提供即时反馈比向服务器保存额外请求更有用。在大多数情况下(就像你的情况一样)你仍然需要在之后使查询无效,因为你手动添加的 post 没有 id,所以你应该将它与来自 post 的列表同步服务器。所以要非常小心,因为如果你从那个页面的其他地方读取那个 id,它将是未定义的并且会抛出错误。所以在一天结束时,你的突变不是乐观更新的好候选者,你应该小心处理所有可能出现的问题 posts 值 post 没有 id在其中(而不是像跟随动作那样只是改变数据库中的布尔值,如果请求不成功,你可以自信地改变缓存并撤消它)。因此,如果我们假设您可以处理该问题,您的 useMutation 挂钩将是这样的:

return useMutation(
  (payload) => {
    queryClient.setQueryData("posts", (oldData) => {
      return [...oldData, payload];
    });
    return createPost(payload);
  },
  {
    onSettled: () => {
      queryClient.invalidateQueries("posts");
    },
  }
);