查询
使用 useQuery 钩子获取数据
本文展示了如何使用GraphQL在 React 中获取数据,并使用useQuery
钩子将结果附加到您的 UI。您还将了解为什么 Apollo 客户端通过跟踪错误和加载状态来简化数据管理代码。
先决条件
本文假设您熟悉构建基本的 GraphQL查询。如果您需要复习,我们建议阅读 此指南。您还可以针对 Apollo 的 全栈教程服务器构建示例查询。
本文还假设您已经设置好了 Apollo Client 并将 React 应用包裹在 ApolloProvider
组件中。有关更多信息,请参阅 入门指南。
要跟随下面的示例,请打开我们 入门项目 和 示例 GraphQL 服务器 在 CodeSandbox 上。您可以在 此处 查看应用的完整版本。
执行查询
在 Apollo 应用中,执行查询的主要 API 是 useQuery
React 钩子。要在 React 组件中运行查询,请调用 useQuery
并传入一个 GraphQL 查询 字符串。当组件渲染时,useQuery
会从 Apollo Client 返回一个对象,其中包含 Apollo Client 的 loading
、error
和 data
属性,您可以使用这些属性来渲染您的 UI。
注意:在 Apollo Client >= 3.8 版本中,Suspense 数据获取钩子可用于查询数据,在 <Suspense />
边界内使用 React 18 的新并发渲染模型。有关更多信息,请参阅 Apollo Client 的 Suspense 文档。
让我们来看一个例子。首先,我们将创建一个名为 GraphQL 查询的 GET_DOGS
。请记住,使用 gql
函数将包含查询的 query 字符串包装在查询文档中:
import { gql, useQuery } from '@apollo/client';const GET_DOGS = gql`query GetDogs {dogs {idbreed}}`;
接下来,我们将创建一个名为Dogs
的组件。在其中,我们将把我们的GET_DOGS
查询传递给useQuery
钩子:
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>);}
随着query
的执行以及loading
、error
和data
的值变化,Dogs
组件可以智能地根据query
的状态渲染不同的UI元素:
- 只要
loading
为true
(表示query
仍在进行中),组件就会显示一个Loading...
通知。 - 当
loading
为false
且没有error
时,查询已完成。该组件将渲染一个下拉菜单,菜单中填充了服务器返回的狗品种列表。
当用户从填充的下拉菜单中选择一个狗品种时,选择内容将通过提供的onDogSelected
函数发送到父组件。
在下一步中,我们将下拉菜单与一个更复杂的query
关联,该query
使用GraphQL 变量。
缓存查询结果
每次Apollo Client从您的服务器获取query
结果时,它都会自动将那些结果本地缓存起来。这使得随后执行的相同query
变得非常快。
为了查看此缓存效果,让我们构建一个新的组件,命名为DogPhoto
。 DogPhoto
接受一个名为breed
的属性,它反映了我们的Dogs
组件中下拉菜单的当前值:
const GET_DOG_PHOTO = gql`query Dog($breed: String!) {dog(breed: $breed) {iddisplayImage}}`;function DogPhoto({ breed }) {const { loading, error, data } = useQuery(GET_DOG_PHOTO, {variables: { breed },});if (loading) return null;if (error) return `Error! ${error}`;return (<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />);}
请注意,我们这次为useQuery
钩子提供了配置选项(variables
)。variables
选项是一个对象,包含了我们想要传递给我们的GraphQL query的variables
。在这种情况下,我们希望传递当前选择的下拉菜单中的breed
。
从下拉列表中选择 bulldog
以查看其照片。然后切换到另一种犬种,然后再切换 回 bulldog
。您将注意到,第二次时,bulldog 的照片会立即加载。这是缓存的作用!
接下来,让我们学习一些确保我们缓存的驻留数据是新鲜的技术。
更新缓存的查询结果
有时,您想确保您的 查询's 缓存数据与您的 服务器 数据保持最新。 Apollo 客户端支持两种策略: 轮询 和 重新获取。
轮询
轮询通过定期(以指定的时间间隔)执行您的 查询 来提供与服务器近乎实时同步。要启用查询的轮询,请将 pollInterval
配置选项传递给 useQuery
钩子,使用毫秒表示间隔:
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 }} />);}
将 pollInterval
设置为 500,我们将每 0.5 秒从服务器获取当前犬种的照片。请注意,如果您将 pollInterval
设置为 0
,该 查询 将不会轮询。
您还可以使用 startPolling
和 stopPolling
函数(由 useQuery
钩子返回)动态开始和停止轮询。使用这些函数时,请将 pollInterval
配置选项作为 startPolling
函数的参数。
重新获取
重新获取允许您在响应特定用户操作时刷新 查询 结果,而不是使用固定的时间间隔。
让我们向我们的 DogPhoto
组件添加一个按钮,该按钮在点击时调用我们的 查询 的 refetch
函数。
您可以可选地向 refetch
函数提供一个新的 variables
对象。如果您避免传递 variables
对象,仅使用 refetch()
,则查询将使用其在上次执行中使用的相同的 variables
。
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()}>Refetch new breed!</button></div>);}
点击按钮并注意 UI 使用新的犬种照片更新。重新获取是确保新鲜数据的一种优秀方式,但它在加载状态中引入了一些复杂度。在下一节中,我们将介绍处理复杂加载和错误状态的战略。
向 refetch
提供新的变量
您可以这样调用 refetch
并提供一个新的一组变量:
<buttononClick={() =>refetch({breed: 'dalmatian', // Always refetches a dalmatian instead of original breed})}>Refetch!</button>
如果您为原始 查询 的某些变量提供了新值,但不是所有的变量,则 refetch
使用省略的每个变量的原始值。
检查加载状态
我们已经看到 useQuery
钩子揭示了我们的 query 当前加载状态。这对于查询首次加载时很有用,但当我们正在重新抓取或轮询时,我们的加载状态会发生什么变化呢?
让我们回到上一节中的重新抓取示例。如果您点击重新抓取按钮,您会看到组件直到新的数据到来之前不会重新渲染。如果我们想向用户表明我们正在重新抓取照片,怎么办?
通过 networkStatus
属性,useQuery
钩子的结果对象提供了关于 query 状态的详细信息的粒度。为了利用这些信息,我们将 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()}>Refetch!</button></div>);}
启用此选项还可以确保 loading
的值相应更新,即使您不想使用 networkStatus
属性提供的更细粒度的信息。
networkStatus
属性是一个代表不同加载状态的 NetworkStatus
枚举。重新抓取由 NetworkStatus.refetch
表示,而对于轮询和分页也有相应的值。有关所有可能的加载状态的完整列表,请查看 源代码。
要查看我们刚刚构建的完整版本的 app,请查看 CodeSandbox 这里。
检测错误状态
您可以通过将 errorPolicy
配置选项提供给 useQuery
钩子来自定义 query 错误处理。默认值是 none
,这告诉 Apollo Client 将所有 GraphQL 错误视为运行时错误。在这种情况下,Apollo Client 会丢弃服务器返回的任何 query 响应数据,并将 error
属性设置为 useQuery
结果对象的
如果您将 errorPolicy
设置为 all
,useQuery
将 不 丢弃查询响应数据,从而使您能够渲染部分结果。
有关更多信息,请参阅 处理操作错误。
使用 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>);}
ⓘ 注意
设置获取策略
const { loading, error, data } = useQuery(GET_DOGS, {fetchPolicy: 'network-only', // Doesn't check cache before making a network request});
nextFetchPolicy
自3.1
const { loading, error, data } = useQuery(GET_DOGS, {fetchPolicy: 'network-only', // Used for first executionnextFetchPolicy: 'cache-first', // Used for subsequent executions});
nextFetchPolicy
函数
如果您想默认应用单个 nextFetchPolicy
,因为您发现自己经常手动为大多数查询提供 nextFetchPolicy
,您可以在创建您的 ApolloClient
实例时配置 defaultOptions.watchQuery.nextFetchPolicy
:
new ApolloClient({link,client,defaultOptions: {watchQuery: {nextFetchPolicy: 'cache-only',},},});
此配置适用于所有 client.watchQuery
调用以及未配置 nextFetchPolicy
的 useQuery
调用。
如果您想更精细地控制 nextFetchPolicy
的行为,您可以提供一个函数而不是 WatchQueryFetchPolicy
字符串:
new ApolloClient({link,client,defaultOptions: {watchQuery: {nextFetchPolicy(currentFetchPolicy) {if (currentFetchPolicy === 'network-only' ||currentFetchPolicy === 'cache-and-network') {// Demote the network policies (except "no-cache") to "cache-first"// after the first request.return 'cache-first';}// Leave all other fetch policies unchanged.return currentFetchPolicy;},},},});
此 nextFetchPolicy
函数会在每次请求后被调用,并使用 currentFetchPolicy
参数决定如何修改抓取策略。
除了每次请求后会被调用,您的 nextFetchPolicy
函数也会在变量变化时被调用,这通常会导致 fetchPolicy
重置为其初始值,这对于触发以 cache-and-network
或 network-only
抓取策略开始的查询的新网络请求非常重要。
为了自己拦截和处理 variables-changed
的情况,您可以使用传递给您的 nextFetchPolicy
函数的第二个参数的 NextFetchPolicyContext
对象:
new ApolloClient({link,client,defaultOptions: {watchQuery: {nextFetchPolicy(currentFetchPolicy,{// Either "after-fetch" or "variables-changed", indicating why the// nextFetchPolicy function was invoked.reason,// The rest of the options (currentFetchPolicy === options.fetchPolicy).options,// The original value of options.fetchPolicy, before nextFetchPolicy was// applied for the first time.initialFetchPolicy,// The ObservableQuery associated with this client.watchQuery call.observable,}) {// When variables change, the default behavior is to reset// options.fetchPolicy to context.initialFetchPolicy. If you omit this logic,// your nextFetchPolicy function can override this default behavior to// prevent options.fetchPolicy from changing in this case.if (reason === 'variables-changed') {return initialFetchPolicy;}if (currentFetchPolicy === 'network-only' ||currentFetchPolicy === 'cache-and-network') {// Demote the network policies (except "no-cache") to "cache-first"// after the first request.return 'cache-first';}// Leave all other fetch policies unchanged.return currentFetchPolicy;},},},});
为了调试这些 nextFetchPolicy
转换,在函数体内添加 console.log
或 debugger
语句可能很有用,以便了解函数被调用的时间和原因。
支持的抓取策略
名称 | 描述 |
---|---|
| Apollo 客户端首先对缓存执行 query。如果所有请求的数据都存在于缓存中,则返回该数据。否则,Apollo 客户端对您的 GraphQL 服务器执行查询,并在将其缓存后返回数据。 优先最小化应用程序发送的网络请求数量。 这是默认的抓取策略。 |
| Apollo 客户端仅对缓存执行 query。在这种情况下,永远不会向服务器查询。 如果缓存不包含所有请求的字段数据,则 |
| Apollo 客户端同时对缓存和您的 GraphQL 服务器执行完整的 query。如果服务器端查询更改了缓存的字段,则会自动更新。 提供快速响应的同时,还有助于确保缓存的数據與服務器數據一致性。 |
| Apollo Client将对您的GraphQL服務器执行完整的查询,但在查询之前不先檢查緩存。query's結果'將存儲在緩存中。 優先保持與服務器數據的一致性,但當緩存數據可用時不能提供幾乎瞬時的響應。 |
| 與 |
| 使用與 |
useQuery
API
以下列出了useQuery
鉤子支持的可選參數和結果字段。
大部分的useQuery
調用可以省略大多數這些參數,但了解它們的存在是有用的。要學習更多有關含使用範例的useQuery
鉤子API,請參考API參考。
參數
以下列出useQuery
鉤子接受的參數:
ApolloClient<any>
使用此ApolloClient
實例來執行query。
默認情況下,使用從上下文傳遞的實例,但您可以這裡提供不同的實例。
ErrorPolicy
(data: TData) => void
當query無錯誤成功完成(或當errorPolicy
為ignore
並返回部分數據時)調用的回調函數。
此函數將接受query的結果data
(error: ApolloError) => void
當query遇到一個或多個錯誤(除非errorPolicy
是ignore
)時,將調用的回調函數。
此函数接受一个ApolloError
对象,该对象包含一个networkError
对象或一个graphQLErrors
数组,取决于发生的错误。
布尔值
如果为真,则不执行query
。
默认值为false
。
TVariables
一个包含所有您查询需要执行的GraphQL
变量query
的对象。
对象中的每个键对应一个变量名
,该键的值对应变量值。
DefaultContext
如果您正在使用Apollo Link,此对象是传递到您的链中的context
对象
如果true
,则相关组件在query
中断开始或出现网络错误时重新渲染。
默认值为false
。
指定query
轮询更新的结果的时间间隔(以毫秒为单位)。
默认值是0
(无轮询)。
() => boolean
每当在轮询期间发生重试时,都会调用回调函数。如果该函数返回true
,则跳过重新尝试重试,直到下一个轮询间隔。
布尔值
传递false
跳过在服务端渲染期间执行query
。
WatchQueryFetchPolicy
WatchQueryFetchPolicy
默认为 options.fetchPolicy 的初始值,但可以显式配置以指定要重新查看查询FetchPolicy,以便在变量变化时(除非 nextFetchPolicy 干预)。
WatchQueryFetchPolicy | ((this: WatchQueryOptions<TVariables, TData>, currentFetchPolicy: WatchQueryFetchPolicy, context: NextFetchPolicyContext<TData, TVariables>) => WatchQueryFetchPolicy)
指定查询完成后所使用的 FetchPolicy
。
RefetchWritePolicy
指定是否将 NetworkStatus.refetch
操作与传入的字段数据合并到现有数据中,还是覆盖现有数据。覆盖可能是更优选的,但合并是目前默认行为,以与 Apollo Client 3.x 的向后兼容。
如果 true
,查询在缓存中不包含查询的所有字段的查询结果时可以返回部分结果。
默认值为false
。
⚠️ 已弃用
使用 canonizeResults
可能会导致内存泄漏,所以我们通常不建议再使用此选项。Apollo Client 的未来版本将包含一个类似功能,但不存在内存泄漏的风险。
在返回之前对缓存结果进行规范化的选项。规范化会占用一些额外的时间,但可以加快未来的深度比较速度。默认为 false。
⚠️ 已弃用
由于更一致地应用获取策略,在 Apollo Client 3 中设置此选项是不必要的。它可能在未来的版本中删除。
如果 true
,则导致如果检测到的查询结果为部分结果,则在查询时进行重新获取。
默认值为false
。
结果
调用 useQuery
钩子后返回一个结果对象,该对象具有以下属性。此对象包含您的查询结果,以及一些用于重新获取、动态轮询和分页的辅助函数。
TData | undefined
一个对象,包含 GraphQL 查询完成后的结果。
此值可能为 undefined
,如果查询产生一个或多个错误(取决于查询的 errorPolicy
)。
ApolloError
如果查询产生了一个或多个错误,此对象包含一个或多个 graphQLErrors
的数组或单个 networkError
。否则,此值是 undefined
。
有关更多信息,请参阅 处理操作错误.
TData
包含此查询最近一次执行的结果的对象。
如果是查询的第一个执行,则此值未定义
。
TVariables | undefined
包含为查询提供的变量的对象。
布尔值
如果true
,则相关延迟查询已执行。
此字段仅在useLazyQuery
返回的
ApolloClient<any>
执行查询的Apollo Client实例。可用于手动执行后续查询或向缓存写入数据。
布尔值
如果true
,则查询还在进行中,结果尚未返回。
NetworkStatus
与notifyOnNetworkStatusChange
选项一起使用。
<TFetchData = TData, TFetchVars extends OperationVariables = TVariables>(fetchMoreOptions: FetchMoreQueryOptions<TFetchVars, TFetchData> & { updateQuery?: (previousQueryResult: TData, options: { fetchMoreResult: TFetchData; variables: TFetchVars; }) => TData; }) => Promise<ApolloQueryResult<TFetchData>>
一个帮助您获取分页列表字段的下一组结果的函数(。
(variables?: Partial<TVariables>) => Promise<ApolloQueryResult<TData>>
一个使您能够重新执行查询的函数,可选地传入新的变量
。
为了保证fetchPolicy
设置为network-only
(除非原始查询的fetchPolicy
是no-cache
或cache-and-network
,后者也会保证进行网络请求)。
另请参阅重新查询。
(pollInterval: 数字) => 无
一个函数,指示查询在指定的时间间隔(以毫秒为单位)重新执行。
() => 无
一个函数,指示查询停止轮询,在先前的startPolling
调用后。
<TSubscriptionData = TData, TSubscriptionVariables 扩展 OperationVariables = TVariables>(options: SubscribeToMoreOptions<TData, TSubscriptionVariables, TSubscriptionData>) => () => 无
一个函数,允许你执行一个订阅,通常用于订阅查询中包含的特定字段
此函数返回另一个函数,你可以调用它来终止订阅。
<TVars 扩展 OperationVariables = TVariables>(mapFn: (previousQueryResult: TData, options: Pick<WatchQueryOptions<TVars, TData>, "variables">) => TData) => 无
一个函数,允许你在不执行后续GraphQL操作的情况下更新查询的缓存结果。
有关更多信息,请参见使用updateQuery和updateFragment。
ObservableQuery<TData, TVariables>
hook所使用的内部ObservableQuery
的引用。
ReadonlyArray<GraphQLFormattedError>
⚠️ 已弃用
该属性将在Apollo Client的下一个版本中被移除。请使用error.graphQLErrors
代替。
下一步
现在你已经了解了如何使用useQuery
hook获取数据,了解如何使用useMutation
hook更新你的数据!
之后,了解一些其他有用的Apollo Client特性: