使用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
,并用以下内容替换其内容:
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) {idtoken}}`;
我们 LOGIN_USER
定义看上去与前一部分查询十分相似,只不过它替换了 query
一词,改用 mutation
。我们在 login
的响应中接收一个 User
对象,其中包括我们将会使用到的两个 字段:
- 用户的
id
,它将会用来在未来的查询中获取特定于用户的数据。 - 会话
token
,它将会用来“验证”未来的 GraphQL 操作。
运用 useMutation
钩子
我们将使用 Apollo Client 的 useMutation
React Hook 来执行我们的 LOGIN_USER
mutation。与 useQuery
一样,该钩子的结果提供了属性,有助于我们填充和呈现组件,贯穿整個 mutation 的执行过程。
与 useQuery
不同, useMutation
不会在其组件呈现后立即执行其 操作。 相反,此 Hook 返回了 突变函数,该函数可执行 mutation,而不管我们何时调用该函数(例如,在用户提交表单时)。
将以下内容添加到 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
)中的第一个对象是我们调用以执行 mutation 的突变函数。我们将此函数传递给LoginForm
组件。 - 元组中的第二个对象与
useQuery
返回的结果对象类似,包括 字段,以此获取 操作 的loading
和error
状态以及 操作 的结果data
。
现在,每当用户提交登录表单,我们的 login
mutation 就会被调用。用户的 token
存储在内存缓存中, 但 我们希望该代币可以在同一浏览器中的多次访问中使用。我们接下来来处理这件事。
持久化用户的令牌和 ID
在调用 useMutation
时,我们可包含 onCompleted
回调函数。这使我们能够在 mutation 的结果数据可用时立即与其交互。我们将使用此回调函数持久化用户的 token
和 id
。
修改 useMutation
在 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
头信息
我们的客户端应随它发送到我们服务器的每个 GraphQL 操作 提供用户的令牌。这使服务器能够验证用户是否有权执行他们正在尝试执行的操作。
在 index.tsx
中,让我们修改 ApolloClient
的构造函数,以定义应用于每个 GraphQL 请求的默认 headers
集:
const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({cache,uri: "https://127.0.0.1:4000/graphql",headers: {authorization: localStorage.getItem("token") || "",},});
当解析 操作 不需要令牌时(例如获取 launches 的列表),我们的服务器可以忽略令牌,因此我们的客户端在每个请求中包含令牌也是可以的。
启用登录表单
我们已完成定义我们的 login
mutation,但我们尚未显示使用户能够执行该 mutation 的表单。由于我们将在本地存储用户令牌,因此我们将使用 Apollo Client 的本地状态 API,以便在下个部分中支持该表单的一些逻辑。