发布于 2026-01-06 1 阅读
0

更新 Apollo Cache 后,您的用户界面将立即受益。

更新 Apollo Cache 后,您的用户界面将立即受益。

我非常喜欢 Apollo 团队开发的产品,尤其是Apollo Client。它最初只是一个简单的工具,用于连接前端数据需求和 GraphQL 服务器,但如今,它已发展成为一套完整的客户端数据管理解决方案。尽管使用 GraphQL可以减少对本地状态的需求,但 Apollo 仍然提供了一个类似 Redux 的“全局”存储,您可以通过 GraphQL 接口访问它。如果您想了解更多关于如何使用Apollo 实现客户端状态的信息,请点击此处阅读。

我不想讨论本地解析器或仅限前端的状态,而是想谈谈 Apollo 从服务器获取 GraphQL 数据时使用的底层缓存,特别是如何利用它来提升UI 的响应速度智能化程度。Apollo 官方文档中关于如何与缓存交互的内容很不错,但我觉得它离实际应用场景还有一段距离。

简单的 CRUD

我将创建一个简单的愿望清单 React 应用(虽然大部分内容也适用于其他框架),它只是一个简单的物品 { title: string, price: int }CRUD 操作。像大多数实际应用一样,它包含一个物品列表、一个删除按钮、一个编辑表单和一个添加表单。我主要想展示的是,如何在列表发生变更后更新列表,而无需从服务器重新获取列表。

我将使用我用Codesandbox创建的这个简单服务器(这帮家伙真是太棒了!),唯一需要注意的是,在编辑mutation 中,你的服务器应该返回更新后的资源,稍后你会明白原因。我会添加一些模拟数据,以便我们立即查看结果。

我假设您已经知道如何搭建一个基于 Apollo Client 和 React 的 Apollo 应用。如果还不清楚,请点击这里查看。

所以,我将逐字逐句地讲解CRUD(出于教育目的,记为 RUCD)以及您可能需要使用的功能。



const GET_ITEMS = gql`
  {
    items {
      id
      title
      price
    }
  }
`;

function Wishlist() {
  const { loading, error, data } = useQuery(GET_ITEMS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error :(</p>;

  return data.items.map(item => {
    const { id, title, price } = item;
    return (
      <div key={id}>
        {title} - for ${price} -{" "}
      </div>
    );
  });
}



Enter fullscreen mode Exit fullscreen mode

就是这样!我们的读取查询非常简单:它不接受搜索参数、分页元数据,也不使用游标。为了演示方便,这样写比较好,但如果您需要更复杂情况的帮助,我稍后会提供补充说明。仅凭这个配置,我们的列表就已经准备好在后续写入时响应缓存变化了。

图片

注意:使用 `<string> useQuery` 或<Query>`<string>`withQuery在这里至关重要,因为它将数据与缓存关联起来。如果您使用其他解决方案,例如 `<string>`useApolloClient和 `<string>`,那么client.query这些解决方案将无法正常工作。

编辑

我们将创建编辑组件,这是最简单的例子,因为编辑项目后列表会自动更新,无需任何额外操作__typename。Apollo Cache 默认使用键值对(+id_id)作为每个缓存对象的主键。每次服务器返回数据时,Apollo 都会检查新数据是否替换了缓存中已有的数据。因此,如果您的editItemmutation 返回了完整的更新Item对象,Apollo 会发现缓存中已经存在该项目(来自您获取列表时),更新缓存,并触发useQuery更新data,最终实现Wishlist重新渲染。



const EDIT_ITEM = gql`
  mutation($id: Int, $item: ItemInput) {
    editItem(id: $id, item: $item) {
      id
      title
      price
    }
  }
`;

const EditItem = ({ item: { title, price, id } }) => {
  const [editItem, { loading }] = useMutation(EDIT_ITEM);

  return (
    <ItemForm
      disabled={loading}
      initialPrice={price}
      initialTitle={title}
      onSubmit={item => {
        editItem({
          variables: {
            id,
            item
          }
        });
      }}
    />
  );
};


Enter fullscreen mode Exit fullscreen mode

完成!当用户点击Submit编辑表单上的按钮时,变更将被触发,Apollo 客户端将收到更新后的数据,并且该项将在列表中自动更新。

创造

让我们来创建AddItem组件。这次有点不同,因为当我们从服务器收到响应(新创建的列表Item)时,Apollo 并不知道列表是否应该更新(有时也确实不应该更新)。为此,我们需要以编程方式将新项添加到列表中,而钩子函数的一个参数useMutation就是update专门用于此目的的函数。

更新缓存所需的步骤如下:

  1. 从 Apollo 缓存中读取数据(我们将使用相同的GET_ITEMS查询语句)
  2. 更新推送我们新产品的商品列表
  3. 将数据写回 Apollo 缓存(也指查询GET_ITEMS

之后,Apollo Client 会注意到该查询的缓存已更改,并且最终还会更新我们的愿望清单。



const ADD_ITEM = gql
mutation($item: ItemInput) {
addItem(item: $item) {
id
title
price
}
}
;

const AddItem = () => {
const [addItem, { loading }] = useMutation(ADD_ITEM);

return (
<ItemForm
disabled={loading}
onSubmit={item => {
addItem({
variables: {
item
},
update: (cache, { data: { addItem } }) => {
const data = cache.readQuery({ query: GET_ITEMS });
data.items = [...data.items, addItem];
cache.writeQuery({ query: GET_ITEMS }, data);
}
});
}}
/>
);
};

Enter fullscreen mode Exit fullscreen mode




删除

删除操作与创建操作非常相似。为了简化操作,我将把它放在愿望清单组件中,并添加一些属性/状态以增强应用程序的整体功能。



const DELETE_ITEM = gql
mutation($id: Int) {
deleteItem(id: $id) {
id
title
price
}
}
;

function Wishlist({ onEdit }) {
const { loading, error, data } = useQuery(GET_ITEMS);
const [deleteItem] = useMutation(DELETE_ITEM);

if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;

return data.items.map(item => {
const { id, title, price } = item;
return (
<div key={id}>
{title} - for ${price} -{" "}
<button
className="dim pointer mr2"
onClick={e => {
onEdit(item);
}}
>
edit
</button>
<button
className="dim pointer"
onClick={() => {
deleteItem({ variables: { id },
update: cache => {
const data = cache.readQuery({ query: GET_ITEMS });
data.items = data.items.filter(({id: itemId}) => itemId !== id);
cache.writeQuery({ query: GET_ITEMS }, data);
}});
}}
>
delete
</button>
</div>
);
});
}

Enter fullscreen mode Exit fullscreen mode




高级病例

我们的应用程序中经常会遇到更复杂的列表,例如带有搜索分页排序功能的列表。对于这类列表,处理起来会比较复杂,而且很大程度上取决于应用程序的上下文。例如,当删除第四页列表中的一个项目时,我们应该直接从用户界面中删除该项目并显示pageLength - 1剩余的项目,还是应该从下一页获取一个项目并添加进去?

这些情况也很棘手,因为如果提供的查询接收变量,cache.readQuery则还需要这些变量,而这些变量可能无法全局访问。一种方法是使用类似原始查询或Apollo 中返回的变量,但这会导致查询再次访问服务器。如果您想深入了解这个问题,此 issue提供了许多解决方案,特别是用于获取查询上次使用的变量的代码片段。queryrefetchrefetchQueries

最终解决方案

这个应用有一些状态/UI方面的问题,我做了一个最小版本来演示Apollo缓存更新 :)

整个应用程序的开发都在这个 CodeSandbox上。您可能需要 fork这个容器并更新服务器 URL。

图片

欢迎随时联系我 :)

文章来源:https://dev.to/lucis/update-apollo-cache-after-a-mutation-and-get-instant-benefits-on-your-ui-1c3b