加入我们,于10月8日至10日在纽约市参加 GraphQL 联邦和 API 平台工程最新技巧、趋势和新闻的交流。加入我们在纽约市的 GraphQL 研讨会 2024
文档
免费开始

Apollo 客户端的突变

使用 useMutation 钩子修改数据


既然我们已经学会了如何使用 Apollo Client 从后端查询数据,那么自然下一步就是学习如何修改后端数据突变

本篇文章演示了如何使用将自己与 GraphQL 服务器的更新发送useMutation钩子。您还将学习如何在执行突变后更新 Apollo 客户端缓存,以及如何跟踪加载和错误状态。

先决条件

本文假设您熟悉构建基本 。如果您需要复习,我们建议您 阅读此指南

本文还假设您已经设置了 Apollo Client 并将您的 React 应用程序包裹在 ApolloProvider 组件中。有关这些步骤的帮助,请 开始

执行突变

React 的钩子 useMutation 是在 Apollo 应用程序中执行 mutations 的主要 API。

要执行一个 mutation,您首先在 React 组件中调用 useMutation,并传递您要执行 的内容,如下所示:

my-component.jsx
import { gql, useMutation } from '@apollo/client';
// Define mutation
const INCREMENT_COUNTER = gql`
# Increments a back-end counter and gets its resulting value
mutation IncrementCounter {
currentValue
}
`;
function MyComponent() {
// Pass mutation to useMutation
const [mutateFunction, { data, loading, error }] = useMutation(INCREMENT_COUNTER);
}

如上图所示,您使用 gql 函数将 字符串解析成 GraphQL ,然后将它传递给 useMutation

当您的组件渲染时,useMutation 返回一个包含以下内容的元组:

  • 一个可随时调用以执行 mutate 函数
    • useQuery 不同,useMutation 不会在渲染时自动执行其操作。相反,您需要调用这个修改函数。
  • 一个对象,其中包含代表修改执行当前状态的字段(data、loading等)
    • 此对象与useQuery钩子返回的对象类似。具体细节,请参阅结果

示例

假设我们正在创建一个待办事项列表应用,并希望用户能够向其列表中添加条目。首先,我们将创建对应的名为ADD_TODOGraphQL mutation。请记住将GraphQL字符串用gql函数包裹,以解析为查询文档:

add-todo.jsx
import { gql, useMutation } from '@apollo/client';
const ADD_TODO = gql`
mutation AddTodo($type: String!) {
addTodo(type: $type) {
id
type
}
}
`;

接下来,我们将创建一个名为AddTodo的组件,它代表待办事项列表的提交表单。在其中,我们将ADD_TODO mutation传递给useMutation钩子:

add-todo.jsx
function AddTodo() {
let input;
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO);
if (loading) return 'Submitting...';
if (error) return `Submission error! ${error.message}`;
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
addTodo({ variables: { type: input.value } });
input.value = '';
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Add Todo</button>
</form>
</div>
);
}

在这个示例中,我们的表单的onSubmit处理程序调用由useMutation钩子返回的修改函数(名为addTodo)。

请注意,此行为与useQuery不同,它会立即执行其操作。这是因为mutations常在用户操作时执行(例如在这个例子中提交表单)。

提供选项

useMutation钩子接受一个options对象作为其第二个参数。以下是一个提供默认值的GraphQL variables的示例:

const [addTodo, { data, loading, error }] = useMutation(ADD_TODO, {
variables: {
type: "placeholder",
someOtherVariable: 1234,
},
});

所有受支持的选项列在选项中。

您可以直接向您的修改函数提供选项,如上面示例片段所示:

addTodo({
variables: {
type: input.value,
},
});

在这里,我们使用variables选项提供任何GraphQL 所需的变量的价值(具体来说,type的创建待办事项项)。

选项优先级

如果同时向useMutation和您的mutate函数提供相同的选项,则mutate函数的值具有优先级。在variables选项的具体情况下,两个对象会进行 shallow合并,这意味着只向useMutation提供的变量将被保留在结果对象中。这可以帮助您为变量设置默认值。

在上面的示例代码段中,input.value会覆盖"placeholder"作为type的值。变量someOtherVariable的值(1234)将被保留。

跟踪突变状态

除了mutate函数之外,useMutation钩子返回一个表示突变执行当前状态的。该对象中的字段(在结果中列出)包括指示mutate函数是否已被调用以及突变的结果是否正在加载中的布尔值。

上面的示例将对象中的loadingerror字段解构,以根据当前突变的状态对AddTodo组件进行不同的渲染:

if (loading) return 'Submitting...';
if (error) return `Submission error! ${error.message}`;

useMutation钩子还支持onCompletedonError选项,如果您喜欢使用回调。 查看API参考。

重置突变状态

useMutation返回的突变结果对象中包含一个reset函数:

const [login, { data, loading, error, reset }] = useMutation(LOGIN_MUTATION);

调用reset将重置突变的结果回到其初始状态(即,在调用mutate函数之前)。您可以使用此功能启用用户在UI中忽略突变结果数据或错误。

调用reset不会删除任何突变执行返回的缓存数据。它只会影响与useMutation钩子相关的状态,导致相应的组件重新渲染。

function LoginPage () {
const [login, { error, reset }] = useMutation(LOGIN_MUTATION);
return (
<>
<form>
<input class="login"/>
<input class="password"/>
<button onclick={login}>Login</button>
</form>
{
error &&
<LoginFailedMessageWindow
message={error.message}
onDismiss={() => reset()}
/>
}
</>
);
}

更新本地数据

当您执行一个突变时,您会修改后端数据。通常,然后您希望更新您的本地缓存数据以反映后端修改。例如,如果您执行一个将项添加到您的待办事项列表的突变,您还希望该项目出现在您的列表缓存副本中。

支持的方法

更新您的本地数据的直接方法是对可能受突变影响的任何查询进行重新查询。然而,这种方法需要额外的网络请求。

如果你的 mutation 返回了所有修改的对象和字段,你可以 直接更新缓存 而不 需要进行任何后续的网络请求。然而,随着你的 mutations 变得更加复杂,这种方法会变得更加复杂。

如果你刚刚开始使用 Apollo Client,我们建议重新查询以更新缓存的本地数据。完成后,你可以通过直接更新缓存来提高应用程序的响应速度。

重新查询

如果你知道在特定的 mutation 后通常需要重新查询某些查询,你可以在该 mutation 的选项中包含一个 refetchQueries 数组:

// Refetches two queries after mutation completes
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO, {
refetchQueries: [
GET_POST, // DocumentNode object parsed with gql
'GetComments' // Query name
],
});

你只能重新查询 活动 查询。活动查询是指当前页面上组件使用的查询。如果你想要更新的数据不是由当前页面上的组件获取的,最好是 直接更新缓存

refetchQueries 数组中的每个元素都是以下之一:

  • DocumentNode 对象,使用 gql 函数处理
  • 你之前执行的查询的名称,用作字符串(例如,GetComments
    • 为了按名称引用查询,请确保您的应用中的每个查询都有一个 唯一的 名称。

每个包含的查询都将使用其最近提供的变量集执行。

你可以将 refetchQueries 选项提供给 useMutation 或是突变函数。有关详细信息,请参阅 选项优先级

请注意,在一个有十几个或上百个不同查询的应用中,确定特定 mutation 后应重新查询哪些查询可能会很具挑战性。

直接更新缓存

包含修改的对象在 mutation 响应中

在大多数情况下,mutation 响应应包含 mutation 修改的任何对象。这使 Apollo Client 能够归一化这些对象并按其 __typenameid 字段(默认情况下) 缓存它们。

在上面的例子中,我们的 ADD_TODO mutation 可能会返回以下结构的 Todo 对象:

{
"__typename": "Todo",
"id": "5",
"type": "groceries"
}

Apollo 客户端 自动将 __typename 添加到查询和突变中的每个对象中,默认情况下。

在接收到此响应对象后, Apollo 客户端 使用键 Todo:5 缓存它。如果存在具有此键的缓存的相同对象, Apollo 客户端 将覆盖任何在突变响应中包括的现有字段(其他现有字段将被保留)。

以这种方式返回修改后的对象是使您的缓存与后端同步的一个有用步骤。但是,这并不总是足够的。例如,新缓存的对象不会被自动添加到现在应包含该对象的任何 列表字段 中。为了实现这一点,您可以定义一个 update 函数。)

update 函数

当一个 突变的响应不足以更新 所有 修改过的字段(比如某些列表字段)在您的缓存中时,您可以定义一个 update 函数来在突变后对缓存的文件进行手动更改。

您将一个 update 函数提供给 useMutation,如下所示:

const GET_TODOS = gql`
query GetTodos {
todos {
id
}
}
`;
function AddTodo() {
let input;
const [addTodo] = useMutation(ADD_TODO, {
update(cache, { data: { addTodo } }) {
cache.modify({
fields: {
todos(existingTodos = []) {
const newTodoRef = cache.writeFragment({
data: addTodo,
fragment: gql`
fragment NewTodo on Todo {
id
type
}
`
});
return [...existingTodos, newTodoRef];
}
}
});
}
});
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
addTodo({ variables: { type: input.value } });
input.value = "";
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Add Todo</button>
</form>
</div>
);
}

如图所示,update 函数接收一个代表 Apollo Client 缓存的 cache 对象。该对象提供了对如 readQuery/writeQuery, readFragment/writeFragment, modifyevict 等缓存 API 方法的访问。这些方法使您能够像与 GraphQL 服务器交互一样在缓存上执行 GraphQL 操作。

关于缓存函数的更多支持信息,请参阅 与缓存数据交互

此外,update 函数还会接收一个包含 data 属性的对象,该属性包含突变的结果。您可以使用该值使用 cache.writeQuery, cache.writeFragmentcache.modify 更新缓存。

如果您的突变指定了 乐观响应update 函数会 两次 被调用:一次是获得乐观结果,另一次是在返回突变实际结果时。

在上面的示例中,当 ADD_TODO 突变执行时,新添加并返回的 addTodo 对象在 update 函数运行之前会被自动保存到缓存中。然而,被 GET_TODOS 查询(它监视 ROOT_QUERY.todos 缓存列表)不会自动更新。这意味着 GET_TODOS 查询无法通知新的 Todo 对象,这又意味着查询不会更新以显示新的条目。

为了解决这个问题,我们使用 cache.modify 通过运行 "修改器" 函数,来对缓存进行微创操作 inserting 或 deleting 项。在上面的示例中,我们知道 GET_TODOS 查询的结果存储在缓存中的 ROOT_QUERY.todos 数组中,所以使用 todos 修改器函数来更新缓存的数组,包含对新添加的 Todo 的引用。通过 cache.writeFragment 的帮助,我们获取已添加 Todo 的内部引用,然后将该引用追加到 ROOT_QUERY.todos 数组中。

update函数内部缓存的任何更改都会自动广播给监听此数据更改的查询。因此,您的应用程序的UI将更新以反映这些更新的缓存值。

update后重新抓取

一个update函数试图复制mutation's的后端变更到您客户端的本地缓存中。这些缓存修改将广播给所有受影响的活跃查询,从而自动更新您的UI。如果update函数正确执行,您的用户将立即看到最新数据,而无需等待另一个网络往返。

然而,一个update函数可能通过设置错误的缓存值而导致复制错误。您可以通过重新抓取受影响的活跃查询来“双重检查”您的update函数的修改。为此,首先为您的mutate函数提供一个onQueryUpdated回调函数:

addTodo({
variables: { type: input.value },
update(cache, result) {
// Update the cache as an approximation of server-side mutation effects
},
onQueryUpdated(observableQuery) {
// Define any custom logic for determining whether to refetch
if (shouldRefetchQuery(observableQuery)) {
return observableQuery.refetch();
}
},
})

您的update函数完成后,Apollo Client将针对每个有权访问已更新的缓存字段的活跃查询调用一次onQueryUpdated 一次。在onQueryUpdated内,您可以使用任何自定义逻辑来确定是否要重新抓取相关查询。

要从onQueryUpdated重新抓取一个查询,如上所示,调用return observableQuery.refetch(),否则不需要返回任何值。如果重新抓取的查询响应与您的update函数的修改不同,您的缓存和UI都将自动再次更新。否则,您的用户将看不到任何变化。

有时,可能难以让您的update函数更新所有相关的查询。并非每个mutation都能提供足够的信息让update函数有效地执行其工作。为确保某个查询被包含在内,您可以将onQueryUpdatedrefetchQueries: [...]结合使用:

addTodo({
variables: { type: input.value },
update(cache, result) {
// Update the cache as an approximation of server-side mutation effects.
},
// Force ReallyImportantQuery to be passed to onQueryUpdated.
refetchQueries: ["ReallyImportantQuery"],
onQueryUpdated(observableQuery) {
// If ReallyImportantQuery is active, it will be passed to onQueryUpdated.
// If no query with that name is active, a warning will be logged.
},
})

如果ReallyImportantQuery本已因为您的update函数而被传递给onQueryUpdated,那么它只会传递一次。使用refetchQueries: ["ReallyImportantQuery"]仅保证查询将被包含在内。

如果发现已包含比预期的更多查询,可以在检查ObservableQuery以确定它不需要重新抓取后返回false以跳过或忽略一个查询,从onQueryUpdated返回Promise将导致最终的Promise<FetchResult<TData>>变异关注点(mutation)等待来自onQueryUpdated的任何返回的承诺,消除了对遗留awaitRefetchQueries: true选项的需求。

想要使用 onQueryUpdated API 而不进行任何 mutation,请尝试使用 client.refetchQueries 方法。在独立 client.refetchQueries API 中,refetchQueries: [...] 选项被称为 include: [...],而 update 函数被称为 updateCache以提高清晰度。否则,相同的内部系统同时支撑 client.refetchQueriesmutation 之后

useMutation API

以下列出了 useMutation 钩子支持的选择项和结果 字段。

大多数对 useMutation 的调用都可以省略这些选项中的大多数,但了解它们的存在是有用的。有关 useMutation 钩子 API 的详细信息,包括使用示例,请参阅 API参考

选项

useMutation 钩子接受以下选项:

操作选项

awaitRefetchQueries(可选)

布尔值

如果为 true,则确保在将 mutation 视为已完成之前,完成 refetchQueries 中包含的所有查询。

默认值为 false(异步地重新获取查询)。

指定 mutation 如何处理同时返回 GraphQL 错误和部分结果的响应。

有关详细信息,请参阅 GraphQL 错误策略

默认值为 none,这意味着 mutation 的结果包括错误详情,但 不包括 部分结果。

如果为 true,则不更新 mutation 的数据属性,以包含 mutation 的结果。

默认值为 false

(data: TData, clientOptions?: BaseMutationOptions) => void

一个回调函数,在您的 mutation 在没有错误的情况下成功完成(或者如果 errorPolicyignore 且部分数据返回时)调用。

此函数传入 mutation 的结果 data以及传递给 mutation 的任何选项。

(error: ApolloError, clientOptions?: BaseMutationOptions) => void

mutation 遇到错误时调用的回调函数(除非 errorPolicy 设置为 ignore)。

此函数接收一个包含 ApolloError 对象,无论是包含 networkError 对象还是包含 graphQLErrors 数组的数组,具体取决于发生的错误,以及传递给 mutation 的任何选项。

OnQueryUpdated<any>

捕获缓存由 mutation 更新以及 client.mutate 传递到列表 refetchQueries: [...] 中指定的任何查询的查询的回调函数。

onQueryUpdated 返回一个 Promise 将导致最终 mutationPromise 等待返回的 Promise。返回 false 将忽略查询。

((result: FetchResult<TData>) => InternalRefetchQueriesInclude) | InternalRefetchQueriesInclude

一个数组(或一个返回数组的函数),指定操作后需要重新获取的查询。

数组的每个值可以是以下之一

  • 一个包含要执行的 query 以及任何 variables 的对象

  • 表示要重新获取的 的字符串

一个包含所有 GraphQL 变量,这些变量是 mutation 执行所需的。

对象的每个键对应一个变量名称,该键的值对应变量值。

网络选项
ApolloClient<object>

使用该实例执行 mutationApolloClient 实例。

默认情况下,通过上下文传递的实例被使用,但您也可以在这里提供不同的实例。

如果您使用 Apollo Link,此对象是传递您链接链的 context 对象的初始值。

如果 true,则在网络状态改变或发生网络错误时,将会重新渲染与正在进行中的突变相关联的组件。

默认值为 false

缓存选项
突变缓存策略

如果突变的返回结果不应用缓存在 Apollo 客户端缓存中,可以提供no-cache

默认值是 network-only(意味着结果将被写入缓存)。

与查询不同,突变 不支持 设置除了 fetch policies 以及 no-cache 之外的其他策略。

TData | (vars:TVariables,{ IGNORE }:({ IGNORE:IgnoreModifier;})=> TData | IgnoreModifier)

通过提供一个对象或回调函数,在突变之后被调用,可以让你返回乐观的数据并可选地通过 IGNORE 锁定对象跳过更新,直到突变完成,这可以使得 UI 更新更响应。

更多信息,请参阅 乐观突变结果

MutationUpdaterFunction<TData,TVariables,TContext,TCache>

这是一个在突变完成后用于更新 Apollo 客户端缓存的功能。

更多信息,请参阅 突变后更新缓存

其他选项

为了避免突变根字段 保留敏感信息,Apollo 客户端 v3.4+ 在每次突变完成后自动清除缓存中的任何 ROOT_MUTATION 字段。如果你需要这些信息保留在缓存中,可以通过在突变中传递 keepRootFields: true 来防止其被删除。 ROOT_MUTATION 的结果数据也会传递给突变 update 函数,所以我们建议尽可能使用这种方式获取结果,而不是使用此选项。

MutationQueryReducersMap<TData>

A MutationQueryReducersMap,它是一个从 查询名字到 突变查询还原器的映射。简单来说,此映射定义了如何将突变的成果合并到您应用程序当前监视的查询结果中。

结果

`useMutation`的结果是一个元组,第一个位置是mutate函数,第二个位置是代表突变结果的对象。

您可以通过调用这个mutate函数来从您的UI触发突变。

其他选项
布尔值

如果为 true,则表示突变功能的mutate函数已被调用。

ApolloClient<object>

执行突变的服务器端Apollo客户端实例。

这可用于手动执行后续操作或将数据写入缓存。

TData | null

从您的突变返回的数据。如果 ignoreResultstrue,则可能为 undefined

如果突变产生一个或多个错误,此对象包含一个 graphQLErrors数组或单个 networkError。否则,此值为 undefined

有关更多信息,请参阅 操作错误处理

布尔值

如果为 true,则表示突变当前正在执行中。

() => void

您可以调用此函数来将突变的结果重置为其初始,未被调用的状态。

下一步

`useQuery`和`useMutation钩子一起代表了Apollo客户端执行 GraphQL 操作的核心API。现在您已经熟悉了这两个,您可以开始利用Apollo客户端的完整功能集,包括:

  • 乐观UI:学习如何在服务器返回突变结果之前返回一个乐观响应来提高感知性能。
  • 本地状态:使用Apollo客户端通过执行客户端突变来管理您应用程序的全部本地状态。
  • Apollo中的缓存:深入Apollo客户端缓存及其规范化的情况。理解缓存对编写您的突变的 update 函数非常有帮助!
上一页
挂起
下一页
重新获取
评分本文比率在GitHub上编辑论坛Discord

©2024公司:Apollo Graph Inc.,商号:Apollo GraphQL。

隐私政策

公司