9. 使用突变更新数据
10m

使用useMutation 挂钩更新数据

现在,我们已为 React 应用程序添加多查询,我们来添加一个 突变。该过程类似,有几个重要不同。

type Mutation {
bookTrips(launchIds: [ID]!): TripUpdateResponse!
cancelTrip(launchId: ID!): TripUpdateResponse!
login(email: String): String # login token
}

我们通过执行登录功能来开始。注意,此 接收单个 , email

支持用户登录

注意:为简单起见,我们的示例应用程序未实现带基于密码认证的实际用户帐号。相反地,用户通过递交其电邮地址,从服务器接收相应会话令牌不安全地“登录”。

定义突变

默认情况下,以下代码块使用 TypeScript。可使用每个代码块上方显示的下拉菜单切换至 JavaScript。


如果使用 JavaScript,则在显示 `.ts` 和 `.tsx` 的位置使用 `.js` 和 `.jsx` 文件扩展名。

首先,前往 client/src/pages/login.tsx,并用以下内容替换其内容:

client/src/pages/login.tsx
import React from "react";
import { gql, useMutation } from "@apollo/client";
import { LoginForm, Loading } from "../components";
import * as LoginTypes from "./__generated__/Login";
export const LOGIN_USER = gql`
mutation Login($email: String!) {
login(email: $email) {
id
token
}
}
`;

我们 LOGIN_USER 定义看上去与前一部分查询十分相似,只不过它替换了 query 一词,改用 mutation。我们在 login 的响应中接收一个 User 对象,其中包括我们将会使用到的两个

  • 用户的 id,它将会用来在未来的查询中获取特定于用户的数据。
  • 会话 token,它将会用来“验证”未来的

运用 useMutation 钩子

我们将使用 useMutation React Hook 来执行我们的 LOGIN_USER 。与 useQuery 一样,该钩子的结果提供了属性,有助于我们填充和呈现组件,贯穿整個 的执行过程。

useQuery 不同, useMutation 不会在其组件呈现后立即执行其 。 相反,此 Hook 返回了 突变函数,该函数可执行 ,而不管我们何时调用该函数(例如,在用户提交表单时)。

将以下内容添加到 login.tsx 的底部:

client/src/pages/login.tsx
export default function Login() {
const [login, { loading, error }] = useMutation<
LoginTypes.Login,
LoginTypes.LoginVariables
>(LOGIN_USER);
if (loading) return <Loading />;
if (error) return <p>An error occurred</p>;
return <LoginForm login={login} />;
}
  • useMutation 的结果元组(login)中的第一个对象是我们调用以执行 的突变函数。我们将此函数传递给 LoginForm 组件。
  • 元组中的第二个对象与 useQuery 返回的结果对象类似,包括 ,以此获取 loadingerror 状态以及 的结果 data

现在,每当用户提交登录表单,我们的 login 就会被调用。用户的 token 存储在内存缓存中, 我们希望该代币可以在同一浏览器中的多次访问中使用。我们接下来来处理这件事。

持久化用户的令牌和 ID

在调用 useMutation 时,我们可包含 onCompleted 回调函数。这使我们能够在 的结果数据可用时立即与其交互。我们将使用此回调函数持久化用户的 tokenid

修改 useMutationlogin.tsx 中的调用,使其与以下内容匹配:

client/src/pages/login.tsx
const [login, { loading, error }] = useMutation<
LoginTypes.Login,
LoginTypes.LoginVariables
>(LOGIN_USER, {
onCompleted({ login }) {
if (login) {
localStorage.setItem("token", login.token as string);
localStorage.setItem("userId", login.id as string);
}
},
});

我们的 onCompleted 回调将用户的唯一 ID 和会话令牌存储在 localStorage 中,这样我们下次用户访问我们的应用程序时就可以将这些值加载到内存中。我们将在下一课中添加该功能。

任务!

向所有请求添加 Authorization 头信息

我们的客户端应随它发送到我们服务器的每个 提供用户的令牌。这使服务器能够验证用户是否有权执行他们正在尝试执行的操作。

index.tsx 中,让我们修改 ApolloClient 的构造函数,以定义应用于每个 请求的默认 headers 集:

client/src/index.tsx
const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({
cache,
uri: "https://127.0.0.1:4000/graphql",
headers: {
authorization: localStorage.getItem("token") || "",
},
});

当解析 不需要令牌时(例如获取 的列表),我们的服务器可以忽略令牌,因此我们的客户端在每个请求中包含令牌也是可以的。

任务!

启用登录表单

我们已完成定义我们的 login ,但我们尚未显示使用户能够执行该 的表单。由于我们将在本地存储用户令牌,因此我们将使用 的本地状态 API,以便在下个部分中支持该表单的一些逻辑。

上一步