Apollo GraphQL:类型策略中合并函数的用例?

Apollo GraphQL: use case for merge function in type policy?

我之前曾在 Apollo’s community chat 上问过这个问题,但由于两周后仍未得到答复,所以我将其重新发布在这里。

这个问题是关于Apollo 3.0引入的新缓存的。此更新弃用了本地查询,取而代之的是“类型策略”(或“字段策略”),其中包括几个用于使用缓存的工具,例如可以为每个 GraphQL 类型定义的 merge 函数来指定如何传入数据应与缓存中的现有数据合并。

我想知道哪些用例更适合 merge 函数,而不是通过 writeQuerywriteFragment.

写入预先计算的更新值

假设我有一个包含对象的数组,而这个数组是另一种类型的 属性。例如:

type Person {
  hobbies: [Hobby!]!
}

该应用程序允许我删除、添加和更新此数组中的元素。 hobbies 应该使用 merge 函数吗?以前,我在本地突变解析器中编写了这个逻辑,但这些已被弃用。通过为这些解析器中的每一个定义一个函数,我可以很容易地模仿旧的行为,它包含 add/remove/update 逻辑,并使用 writeFragment 来存储结果。

我可以看到使用 merge 的好处,因为(我认为)它是一个较低的抽象层,但是它可以在传入 hobbies 数组的情况下使用吗?据我所知,它的工作方式意味着我们必须通过传入的输入来推断突变的类型。例如:

这似乎不如在调用 writeFragment.

之前计算 hobbies 的新值的旧方法优雅

使用 merge 有性能优势吗? hobbies 中的现有和不受影响的项目是否在使用 merge 时保留在原位,并在使用 writeFragment 时被覆盖?假设传递给 writeFragment 的新计算数据包含一个数组,其中包含未修改元素的浅表副本。

非常感谢你帮我解决这个问题!

与此同时,我实现了一个类似于 Apollo docs 中概述的合并功能。此函数尽可能多地重用 existing 数组中的项目,否则从 incoming 中获取项目。基于对象 id 检查相等性,它几乎总是出现在我的应用程序中。

这种方法唯一(次要)的缺点似乎是它不处理数组中的项目被更改的情况,但这种操作是在单独的突变中完成的,return 一个数组,无论如何。

为了后人,我的实现如下:

import type { FieldPolicy, Reference } from '@apollo/client'

/**
 * Based on example in Apollo docs
 *
 * @see https://www.apollographql.com/docs/react/caching/cache-field-behavior/#merging-arrays-of-non-normalized-objects
 */
export const mergeArrayByIdFieldPolicy: FieldPolicy<Reference[]> = {
  // eslint-disable-next-line @typescript-eslint/default-param-last
  merge: (existing = [], incoming = [], { readField, mergeObjects }) => {
    const merged = [...incoming]
    const existingIds = existing.map((item) => readField<UUID>('id', item))

    merged.forEach((item, index) => {
      const itemId = readField<UUID>('id', item)
      const existingIndex = existingIds.findIndex((id) => id === itemId)
      if (existingIndex !== -1) {
        merged[index] = mergeObjects(existing[existingIndex], merged[index])
      }
    })
    return merged
  },
}