如何在react-apollo中处理删除操作

36

我有一种像突变的情况

mutation deleteRecord($id: ID) {
  deleteRecord(id: $id) {
    id
  }
}

在另一个地方,我有一份元素列表。

从服务器返回是否有更好的内容,并且应该如何更新列表?

更普遍地说,在Apollo/GraphQL中处理删除的最佳实践是什么?


备忘录:这个页面可能会有用 http://dev.apollodata.com/react/cache-updates.html#updateQueries - derekdreery
17
基本上,你做不到。相反,你会掉发,不停地诅咒Apollo团队,并浏览他们的Github页面上用户提供的一长串妥协性半工作解决方案清单。 - Vincent Cantin
我几乎可以保证,总有一天会有一种方法来使已删除的项目无效,以便Apollo自动重新获取包含它的任何查询,因为当前的做法离完美还很遥远。 - Andy
6个回答

20

我不确定这是一个好的实践方式,但以下是我如何使用updateQueries在react-apollo中处理删除项目的方法:

import { graphql, compose } from 'react-apollo';
import gql from 'graphql-tag';
import update from 'react-addons-update';
import _ from 'underscore';


const SceneCollectionsQuery = gql `
query SceneCollections {
  myScenes: selectedScenes (excludeOwner: false, first: 24) {
    edges {
      node {
        ...SceneCollectionScene
      }
    }
  }
}`;


const DeleteSceneMutation = gql `
mutation DeleteScene($sceneId: String!) {
  deleteScene(sceneId: $sceneId) {
    ok
    scene {
      id
      active
    }
  }
}`;

const SceneModifierWithStateAndData = compose(
  ...,
  graphql(DeleteSceneMutation, {
    props: ({ mutate }) => ({
      deleteScene: (sceneId) => mutate({
        variables: { sceneId },
        updateQueries: {
          SceneCollections: (prev, { mutationResult }) => {
            const myScenesList = prev.myScenes.edges.map((item) => item.node);
            const deleteIndex = _.findIndex(myScenesList, (item) => item.id === sceneId);
            if (deleteIndex < 0) {
              return prev;
            }
            return update(prev, {
              myScenes: {
                edges: {
                  $splice: [[deleteIndex, 1]]
                }
              }
            });
          }
        }
      })
    })
  })
)(SceneModifierWithState);

嗨@vwrobel,感谢您的回答。我想删除缓存中此记录的任何残留(我使用${type}:${id}作为我的缓存键)。这样做也可以吗? - derekdreery
嗨@derekdreery。当我使用updateQuery时,只有指定的代码应用于先前的查询结果。在我发布的代码中,我得到的唯一变化是我的删除项从列表SceneCollections.myScenes中删除了。此外,如果我有另一个查询来获取user {id,sceneCounter},我将不得不手动从updateQueries更新sceneCounter:即使我的DeleteSceneMutation返回更新的user {id,sceneCounter},当我使用updateQueries时,其他查询结果不会被更新。不确定它是否回答了你的问题,我的apollo知识相当有限... - vwrobel
没关系 - 我的知识也有限!! - derekdreery
太棒了,谢谢。当使用“id”调用命名查询时会发生什么?Apollo能否在“updateQueries”中找出要修改的数据集?也就是说,如果存储中已经有从SceneCollections(id:“xyz”)返回的多个结果怎么办? - Jazzy

16

这里有一个类似的解决方案,它不需要使用 underscore.js。它已经在版本号为 2.1.1 的 react-apollo 中进行了测试,并为删除按钮创建了一个组件:

import React from "react";
import { Mutation } from "react-apollo";

const GET_TODOS = gql`
{
    allTodos {
        id
        name
    }
}
`;

const DELETE_TODO = gql`
  mutation deleteTodo(
    $id: ID!
  ) {
    deleteTodo(
      id: $id
    ) {
      id
    }
  }
`;

const DeleteTodo = ({id}) => {
  return (
    <Mutation
      mutation={DELETE_TODO}
      update={(cache, { data: { deleteTodo } }) => {
        const { allTodos } = cache.readQuery({ query: GET_TODOS });
        cache.writeQuery({
          query: GET_TODOS,
          data: { allTodos: allTodos.filter(e => e.id !== id)}
        });
      }}
      >
      {(deleteTodo, { data }) => (
        <button
          onClick={e => {
            deleteTodo({
              variables: {
                id
              }
            });
          }}
        >Delete</button>            
      )}
    </Mutation>
  );
};

export default DeleteTodo;

10

所有这些答案都假定了以查询为导向的缓存管理。

如果我删除ID为1的“用户”,并且该用户在整个应用程序中引用了20个查询,那怎么办?根据上面的答案,我必须假设我将不得不编写代码来更新所有这些缓存。这对于代码库的长期可维护性来说是极其糟糕的,也会使任何重构成为一场噩梦。

我认为最好的解决方案是像apolloClient.removeItem({__typeName: "User", id: "1"})这样的东西:

  • 将缓存中对此对象的任何直接引用替换为null
  • 在任何查询中过滤掉[User]列表中的此项

但它还不存在(目前)

这可能是一个很好的想法,也可能更糟糕(例如,它可能会破坏分页)

有关此问题的有趣讨论:https://github.com/apollographql/apollo-client/issues/899

对于那些手动查询更新,我要小心一点。一开始看起来很有吸引力,但如果您的应用程序增长了,这种方法将不再适用。请至少在其上创建一个稳定的抽象层,例如:

  • 在每个查询旁边定义函数(例如在同一个文件中)- 定义一个可以正确转换它的函数

const MY_QUERY = gql``;

// it's local 'cleaner' - relatively easy to maintain as you can require proper cleaner updates during code review when query will change
export function removeUserFromMyQuery(apolloClient, userId) {
  // clean here
}

然后,收集所有这些更新并在最终更新中调用它们。

function handleUserDeleted(userId, client) {
  removeUserFromMyQuery(userId, client)
  removeUserFromSearchQuery(userId, client)
  removeIdFrom20MoreQueries(userId, client)
}

这正是我的问题,我在缓存中有许多使用相同值的查询/列表。而我真的不想跟踪它们所有。 - Juan Solano

9
对于Apollo v3,这对我有效:
const [deleteExpressHelp] = useDeleteExpressHelpMutation({
  update: (cache, {data}) => {
    cache.evict({
      id: cache.identify({
        __typename: 'express_help',
        id: data?.delete_express_help_by_pk?.id,
      }),
    });
  },
});

新文档中得知:
对于缓存的数组字段(例如上面的Deity.offspring示例),过滤掉悬空引用是非常常见的,因此Apollo Client会自动为没有定义读取函数的数组字段执行此过滤。

2
文档还说在之后应该调用cache.gc()。 - Alexander Danilov

4

个人而言,我会返回一个int表示删除的项目数量。然后使用updateQueries从缓存中移除文档。


2
您能举个使用updateQueries删除记录的例子吗?特别是当它们在树中出现在多个位置时。 - derekdreery
你必须从所有位置移除它吗? - derekdreery
你解决了吗@derekdreery?我还没有弄清楚如何使用updateQueries来删除项目。 - Dunos
不,我还在思考这个问题。我可能会将其作为一个问题提出到库中。 - derekdreery
我们曾经有一个备用的变异结果界面,专门针对这种情况进行了优化,但并不理想。请提交一个问题,我们可以进一步讨论! - stubailo
如果您打开了问题,请在此处放置链接!没关系,我已经找到了这个问题 :) - Dunos

2

当与变异相关的REST API可能返回http 204、404或500时,我面临了选择适当的返回类型的相同问题。

定义任意类型,然后返回null(默认情况下类型可为空)似乎不正确,因为您不知道发生了什么,也就是它是否成功。

返回布尔值解决了这个问题,您知道变异是否起作用,但如果它没有起作用,则缺少一些信息,例如更好的错误消息,您可以在FE上显示该消息,例如,如果我们得到404,我们可以返回“未找到”。

返回自定义类型感觉有点强制,因为它实际上不是您模式或业务逻辑的类型,它只是用于修复REST和GraphQL之间的“通信问题”。

最终我返回一个字符串。我可以返回资源ID / UUID或成功时简单地返回“ok”,并在出现错误时返回错误消息。

不确定这是否是良好的做法或GraphQL惯用语。


我喜欢这种方法。 - lewislbr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接