在 React 中使用 GraphQL (新手指南)
在 React 中使用 GraphQL (新手指南)
What & Why(GraphQL 是什么 & 为什么要使用 GraphQL)
比这之前的开发中,在数据交互中使用的主要是 Restful API 和 RPC API,但是现在的项目中使用的是 GraphQL API ,因此展开了对于 GraphQL 的了解和学习。首先,可以通过这篇文章【REST API 已死,GraphQL 长存】了解一下 GraphQL 的出现解决了什么问题,也就是 what & why — 「一次请求,集齐多种所需数据」。
How(如何在 React 中使用 GraphQL)
Apollo 是一套用于创建 GraphQL服务器和使用 GraphQL API 的工具。在前端对GraphQL的使用中也是基于 Apollo 实现的。针对 React 应用,有支持 react 的 react-apollo 、 apollo-client 和 apollo/react-hooks 等包。目前我们团队主要使用的是 react-apollo 和 apollo-client 。下面就从 react-apollo 和 apollo-client 的使用来开始学习。
apollo-client 的使用
初始化 apollo 客户端
创建客户端 & 下发客户端
index.ts
// import { ApolloClient, ApolloProvider } from 'react-apollo';
// import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
// 实例化 ApolloClient
// 默认情况客户端会发送到相同主机名(域名)下的/graphql端点 new ApolloClient()
const client = new ApolloClient({
cache: new InMemoryCache(), // 开启缓存,Apollo 客户端在获取查询结果后使用它来缓存查询结果。
uri: 'http://my-api.graphql.com', // uri指定我们的 GraphQL 服务器的 URL。
});
// 挂载组件
ReactDOM.render(
<ApolloProvider client={client}>
<MyRootComponent />
</ApolloProvider>,
document.getElementById('App')
)
尝试使用 client.query() 发送查询
gql 是一个模板字符串,能够将字符串解析为 GraphQL 语句
index.ts
import { gql } from '@apollo/client';
client
.query({
query: gql`
query GetLocations {
locations {
id
name
description
photo
}
}
`,
})
.then((result) => console.log(result));
查询(query)
当我们需要对数据进行查询时,就使用 query 操作,对应 http 请求的 get 操作;当需要对数据进行 删除、修改、新增 时,就是用 mutation 操作,对应 http 请求的 post 操作。
useQuery(自动查询)
useQuery 会在组件渲染时执行一次查询。
import { gql, useQuery } from '@apollo/client';
const GET_DOGS = gql`
query GetDogs {
dogs {
id
breed
}
}
`;
function Dogs({ onDogSelected }) {
const { loading, error, data } = useQuery(GET_DOGS);
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
return (
<select name='dog' onChange={onDogSelected}>
{data.dogs.map((dog) => (
<option key={dog.id} value={dog.breed}>
{dog.breed}
</option>
))}
</select>
);
}
useLazyQuery(手动查询)
useLazyQuery 不会立即执行其关联的查询,会在其结果元组中返回一个查询函数,在每次调用该查询函数时才会执行查询。
import React from 'react';
import { useLazyQuery } from '@apollo/client';
function DelayedQuery() {
const [getDog, { loading, error, data }] = useLazyQuery(GET_DOG_PHOTO);
if (loading) return <p>Loading ...</p>;
if (error) return `Error! ${error}`;
return (
<div>
{data?.dog && <img src={data.dog.displayImage} />}
<button onClick={() => getDog({ variables: { breed: 'bulldog' } })}>
Click me!
</button>
</div>
);
}
您可以将查询选项 options 传递给查询函数,就像将它们传递给 useLazyQuery 自身一样。如果同时将特定选项传递给 useLazyQuery 和 查询函数,则传递给查询函数的值优先。这是将默认选项传递给 useLazyQuery 然后在查询函数中自定义这些选项的便捷方式。
查询结果缓存
每当 Apollo 客户端从服务器获取查询结果时,它会自动在本地缓存这些结果(也可以配置关闭缓存)。这使得稍后执行相同查询的速度非常快。
以下是使用 GraphQL 变量的更复杂具体的查询。
const GET_DOG_PHOTO = gql`
query Dog($breed: String!) {
dog(breed: $breed) {
id
displayImage
}
}
`;
function DogPhoto({ breed }) {
const { loading, error, data } = useQuery(GET_DOG_PHOTO, {
variables: { breed },
fetchPolicy: 'no-cache', // 可以通过这个设置关闭默认的缓存行为
});
if (loading) return null;
if (error) return `Error! ${error}`;
return (
<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
);
}
更新缓存结果 — 轮询(Polling)
您希望确保查询的缓存数据与服务器数据保持同步。Apollo Client 为此支持两种策略:轮询和重新获取。
轮询通过以指定的时间间隔定期执行查询来提供与服务器的近实时同步。想要为查询启用轮询,只需要设置 pollInterval
参数(单位毫秒)来设置每次轮询的时间间隔。如果设置 pollInterval
为0,则查询不会轮询。
function DogPhoto({ breed }) {
const { loading, error, data } = useQuery(GET_DOG_PHOTO, {
variables: { breed },
pollInterval: 500,
});
if (loading) return null;
if (error) return `Error! ${error}`;
return (
<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
);
}
可以使用 useQuery hook 返回的 startPolling
和 stopPolling
函数动态启动和停止轮询。使用 startPolling
函数时,需要将 pollInterval
配置选项设置为 startPolling
函数的参数。
startPolling(pollInterval: number) {
...
}
更新缓存结果 — 重新获取(Refetching)
重新获取能够刷新查询结果以响应特定的用户操作,而不是使用固定的时间间隔。例如,我们需要在点击按钮时重新执行上述的查询操作来刷新查询结果,我们可以在调用 refetch
函数时不传递新的查询参数,而直接使用上次的查询参数进行重新获取;也可以重新传入一个新的 variables
对象来进行查询。
如果在 refetch
中为 variables
提供了部分的变量但没有全部提供新的值,refetch
会为每个省略的变量自动使用原始查询中的原始值。
function DogPhoto({ breed }) {
const { loading, error, data, refetch } = useQuery(GET_DOG_PHOTO, {
variables: { breed },
});
if (loading) return null;
if (error) return `Error! ${error}`;
return (
<div>
<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
<button onClick={() => refetch({ breed: 'new_dog_breed' })}>
Refetch new breed!
</button>
</div>
);
}
如果在 refetch
中为 variables
提供了部分的变量但没有全部提供新的值,refetch
会为每个省略的变量自动使用原始查询中的原始值。
refetch 时检查加载状态
我们已经看到这个useQuery钩子暴露了我们查询的当前加载状态。这在第一次加载查询时很有帮助,但是当我们重新获取或轮询时我们的加载状态会发生什么呢?在上个例子中,如果单击重新获取按钮,您将看到组件在新数据到达之前不会重新渲染。如果我们想向用户表明我们正在重新获取照片怎么办?useQuery的返回值中还提供了一个 networkStatus
来标识有关查询状态的信息,同时需要设置 notifyOnNetworkStatusChange: true,
。
import { NetworkStatus } from '@apollo/client';
function DogPhoto({ breed }) {
const { loading, error, data, refetch, networkStatus } = useQuery(
GET_DOG_PHOTO,
{
variables: { breed },
notifyOnNetworkStatusChange: true,
}
);
if (networkStatus === NetworkStatus.refetch) return 'Refetching!';
if (loading) return null;
if (error) return `Error! ${error}`;
return (
<div>
<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
<button onClick={() => refetch({ breed: 'new_dog_breed' })}>
Refetch!
</button>
</div>
);
}
启用 notifyOnNetworkStatusChange
选项还可以确保相应的 loading
值更新值,即使您不想使用networkStatus属性提供的更细粒度的信息。该 networkStatus
属性是一个 NetworkStatus
枚举,代表不同的加载状态。Refetch 用 NetworkStatus.refetch
表示,还有轮询和分页的值。有关所有可能加载状态的完整列表,请查看源代码。
检查错误状态
您可以通过为 useQuery 提供的 errorPolicy 配置选项来自定义查询错误处理。errorPolicy 的默认值为 none,它告诉 Apollo Client 将所有 GraphQL 错误视为运行时错误。在这种情况下,Apollo Client 会丢弃服务器返回的任何查询响应数据,并在结果对象中设置 error 属性。如果设置 errorPolicy 为 all,useQuery 则不会丢弃查询响应数据,允许您呈现部分结果。
none: 如果响应包含 GraphQL 错误,则会将这些错误返回在 error.graphQLErrors 中,并将 data 设置为 undefined,即使服务端在响应中返回了 data 数据。这意味着网络错误和 GraphQL 错误的响应格式是一样的。这是默认的错误策略。
ignore: graphQLErrors 被忽略(error.graphQLErrors未填充),并且任何返回的内容都会被 data 缓存并呈现,就好像没有发生错误一样。
all:data 和 error.graphQLErrors 都被填充,使您能够呈现部分结果和错误信息。
const { loading, error, data, refetch, networkStatus } = useQuery(
GET_DOG_PHOTO,
{
variables: { breed },
notifyOnNetworkStatusChange: true,
errorPolicy: 'all', // none ignore all
}
);
设置 fetch 策略
默认情况下,该useQuery钩子会检查 Apollo 客户端缓存以查看您请求的所有数据是否已在本地可用。如果所有数据都在本地可用,则useQuery返回该数据并且不查询您的 GraphQL 服务器。因此 cache-first
策略是 Apollo Client 的默认获取策略。可以使用 fetchPolicy
给定查询指定不同的获取策略。
您还可以指定查询的 nextFetchPolicy
。如果这样做,fetchPolicy 则用于查询的第一次执行,而 nextFetchPolicy 用于确定查询如何响应未来的缓存更新:
const { loading, error, data } = useQuery(GET_DOGS, {
fetchPolicy: 'network-only', // 在发出网络请求之前不检查缓存
});
// 例如,如果您希望查询始终发出初始网络请求,但此后您可以轻松地从缓存中读取,这将很有帮助。
const { loading, error, data } = useQuery(GET_DOGS, {
fetchPolicy: 'network-only', // 用于第一次执行
nextFetchPolicy: 'cache-first', // 用于后续执行
executions
});
突变(mutation)
useMutation
useMutation React hook 是在 Apollo 应用程序中执行突变的主要 API 。
使用 gql
方法将 mutation 字符串解析为 GraphQL 文档并传递给 useMutation。
import { gql, useMutation } from '@apollo/client';
// Define mutation
// 增加一个后端计数器并获取其结果值
const INCREMENT_COUNTER = gql`
# 增加一个后端计数器并获取其结果值
mutation IncrementCounter {
currentValue
}
`;
function MyComponent() {
// Pass mutation to useMutation
const [mutateFunction, { data, loading, error }] = useMutation(INCREMENT_COUNTER);
}
当您的组件渲染时,useMutation 返回一个元组,其中包括:
- 您可以随时调用以执行突变的突变函数
与 useQuery 不同,useMutation 不会在渲染时自动执行其操作。相反,您调用此突变函数时才会执行操作。 - 具有表示突变执行当前状态的字段的对象(data,loading等)
这个对象类似于 useQuery 钩子返回的对象。
重置突变状态
useMutation 返回的突变结果对象中包括一个 reset
函数。调用 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()}
/>
}
</>
);
}
更新本地数据
执行突变时,您会修改后端数据。通常,您需要更新本地缓存的数据以反映后端修改。例如,如果您执行突变以将项目添加到待办事项列表中,您还希望该项目出现在列表的缓存副本中。
更新本地数据最直接的方法是重新获取任何可能受突变影响的查询。但是,此方法需要额外的网络请求。如果你的变异返回了它修改的所有对象和字段,你可以直接更新你的缓存, 而不需要任何后续的网络请求。但是,随着您的突变变得更加复杂,这种方法的复杂性也会增加。如果您刚刚开始使用 Apollo 客户端,我们建议您重新获取查询以更新您的缓存数据。完成这项工作后,您可以通过直接更新缓存来提高应用的响应能力。
如果您知道您的应用程序通常需要在特定突变后重新获取某些查询,您可以在该突变的选项中添加 refetchQueries
数组:
// 变异完成后重新获取两个查询
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO, {
refetchQueries: [
{query: GET_POST}, // DocumentNode object parsed with gql
'GetComments' // Query name
],
});
refetchQueries
数组中的每个元素都是以下之一:
- query 对象的引用(使用 gql 解析的 DocumentNode 对象 )和 variables
- 您之前执行的查询的名称(例如,GetComments)
要按名称引用查询,请确保您应用的每个查询都有一个唯一的名称。
每个包含的查询都使用其最近提供的一组变量执行。
订阅(Subscriptions)
todo
片段(Fragments)
todo
Reference
- https://chenyitian.gitbooks.io/apollo/content/docs/02.html
- https://www.apollographql.com/docs/react/get-started
- https://www.apollographql.com/docs/react/api/link/apollo-link-retry