【问题标题】:How do I handle deletes in react-apollo我如何处理 react-apollo 中的删除
【发布时间】:2017-03-21 23:03:18
【问题描述】:

我有一个类似的突变

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

在另一个位置,我有一个元素列表。

我可以从服务器返回更好的东西吗?我应该如何更新列表?

更一般地说,在 apollo/graphql 中处理删除的最佳实践是什么?

【问题讨论】:

  • 自我注意:此页面可能有用dev.apollodata.com/react/cache-updates.html#updateQueries
  • TLDR:基本上,你不会。相反,你松开头发,循环诅咒 Apollo 团队,并浏览像你这样的用户在他们的 Github 页面上提供的大量妥协的半工作解决方法。 github.com/apollographql/apollo-client/issues/621
  • 我几乎可以保证,总有一天会有一种方法使已删除的项目无效,这样 Apollo 会自动重新获取包含它的任何查询,因为目前这样做的方法远非完美。

标签: javascript graphql apollo-server react-apollo


【解决方案1】:

对于 Apollo v3,这对我有用:

const [deleteExpressHelp] = useDeleteExpressHelpMutation({
  update: (cache, {data}) => {
    cache.evict({
      id: cache.identify({
        __typename: 'express_help',
        id: data?.delete_express_help_by_pk?.id,
      }),
    });
  },
});

来自new docs

从缓存的数组字段中过滤悬空引用(如上面的 Deity.offspring 示例)非常常见,以至于 Apollo 客户端会自动对未定义读取函数的数组字段执行此过滤。

【讨论】:

  • 文档还说你应该在之后调用 cache.gc()。
【解决方案2】:

当与突变相关的其余 API 可能返回 http 204、404 或 500 时,我在为此类突变选择适当的返回类型时遇到了同样的问题。

定义任意类型然后return null(类型默认可以为空)似乎不正确,因为您不知道发生了什么,这意味着它是否成功。

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

返回自定义类型感觉有点勉强,因为它实际上不是您的架构或业务逻辑的类型,它只是用于解决 rest 和 Graphql 之间的“通信问题”。

我最终返回了一个字符串。如果成功,我可以返回资源 ID/UUID 或简单地“确定”,如果出现错误,则返回错误消息。

不确定这是否是一种好的做法或 Graphql 惯用语。

【讨论】:

  • 我喜欢这种方法
【解决方案3】:

所有这些答案都假设面向查询的缓存管理。

如果我删除了 ID 为 1user 并且该用户在整个应用程序的 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)
}

【讨论】:

  • 这正是我的问题,我在缓存中有许多使用相同值的查询/列表。而且我真的不想跟踪所有这些。
【解决方案4】:

这是一个类似的解决方案,无需 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;

【讨论】:

    【解决方案5】:

    我不确定这是 良好的实践风格,但这是我如何使用 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。当我使用updateQuery 时,只有指定的代码会应用于之前的查询结果。在我发布的代码中,我得到的唯一更改是从列表 SceneCollections.myScenes 中删除了我删除的项目。此外,如果我有其他查询要获取user { id, sceneCounter },我将不得不从updateQueries 手动更新sceneCounter:即使我的DeleteSceneMutation 返回更新的user { id, sceneCounter },当我使用updateQueries 时其他查询结果也不会更新。不确定它是否回答了您的问题,我的阿波罗知识相当有限......
    • 别担心——我的知识也有限!!
    • 这太棒了,谢谢。当使用ids 调用命名查询时会发生什么? Apollo 能弄清楚updateQueries 中要修改哪组数据吗?即,如果您已经在商店中有来自 SceneCollections(id: "xyz") 的多个结果怎么办?
    【解决方案6】:

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

    【讨论】:

    • 您能否举一个使用updateQueries 删除记录的示例:特别是它们在树中多个位置的位置。
    • 您必须从所有位置删除它吗?
    • 你解决了吗@derekdreery?我还没弄清楚如何使用updateQueries 来删除项目
    • 不,我还在想。我可能会在 lib 上将其作为问题提出。
    • 我们曾经有一个替代突变结果界面,该界面针对此类内容进行了优化,但这并不理想。请提出问题,我们可以讨论更多!
    猜你喜欢
    • 2019-07-16
    • 2018-01-28
    • 2017-08-04
    • 2019-09-22
    • 2018-07-11
    • 2018-07-09
    • 2017-09-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多