React Apollo:使用 useQuery 了解 Fetch Policy
这些天我一直在做一个 Apollo 客户项目。我不习惯 GraphQL,所以一开始我很难理解它。
在我正在开发的应用程序中,数据一致性在某些时候出现了问题。
我搜索了一下,我知道阿波罗客户端使用cache-first作为默认获取策略。我将项目中的 fetch 策略更改为no-cache,因为我认为这更适合项目。之后我发现了一些no-cache的错误,我觉得出了点问题。我认为了解有关获取策略的更多信息可能会很好。
我要谈谈
-
使用 useQuery 获取策略
-
更改默认获取策略
我使用nest 准备了一个简单的todo graphql 服务器。没有数据库。该服务器仅使用一个阵列作为存储,我将使用该服务器进行以下测试。
您可以从这个存储库检查后端服务器代码。
我在客户端设置了"@apollo/client": "3.5.8"。
使用useQuery获取策略
useQuery上有六个可用的提取策略。
姓名
描述
缓存优先
Apollo 客户端首先对缓存执行查询。如果缓存中存在所有请求的数据,则返回该数据。否则,Apollo 客户端会针对您的 GraphQL 服务器执行查询,并在缓存数据后返回该数据。优先减少应用程序发送的网络请求数量。这是默认的获取策略。
仅缓存
Apollo 客户端仅针对缓存执行查询。在这种情况下,它永远不会查询您的服务器。如果缓存不包含所有请求字段的数据,则仅缓存查询会引发错误。
缓存和网络
Apollo 客户端对缓存和您的 GraphQL 服务器执行完整查询。如果服务器端查询的结果修改了缓存字段,查询会自动更新。提供快速响应,同时还有助于保持缓存数据与服务器数据一致。
仅限网络
Apollo 客户端对您的 GraphQL 服务器执行完整查询,而无需先检查缓存。查询的结果存储在缓存中。优先考虑与服务器数据的一致性,但在缓存数据可用时无法提供近乎即时的响应。
无缓存
类似于 network-only,除了查询的结果不存储在缓存中。
支持
使用与缓存优先相同的逻辑,除了当基础字段值更改时此查询不会自动更新。您仍然可以使用 refetch 和 updateQueries 手动更新此查询。
来源:Apollo 文档
我将向您展示每个获取策略是如何工作的。
缓存优先
这是一个默认的获取策略,如果缓存中有数据则使用缓存,否则从服务器获取数据。
我为此测试编写了代码。有两个按钮。一个用于创建待办事项,另一个用于显示或隐藏数据表(mount和unmount)。数据表获取数据为useQuery。
这是代码。
import { useCallback, useState } from "react";
import {
ApolloClient,
InMemoryCache,
ApolloProvider,
useQuery,
useMutation,
gql,
} from "@apollo/client";
let suffixIndex = 1;
const GET_TODOS = gql`
query {
getTodos {
id
content
checked
}
}
`;
const CREATE_TODO = gql`
mutation CreateTodo($content: String!) {
ct1: createTodo(content: $content) {
id
content
checked
}
}
`;
const client = new ApolloClient({
uri: "http://localhost:3000/graphql",
cache: new InMemoryCache(),
});
function TodosTable() {
const { data: todosData, loading: todosLoading } = useQuery(GET_TODOS);
if (todosLoading) return <span>Loading...</span>;
return (
<table>
<thead>
<tr>
<th>id</th>
<th>content</th>
<th>checked</th>
</tr>
</thead>
<tbody>
{todosData?.getTodos.map((todo) => (
<tr key={todo.id}>
<td>{todo.id}</td>
<td>{todo.content}</td>
<td>{todo.checked}</td>
</tr>
))}
</tbody>
</table>
);
}
function App() {
const [createTodo] = useMutation(CREATE_TODO);
const [todosTableVisible, setTodosTableVisible] = useState(false);
const handleCreateButtonClick = useCallback(() => {
createTodo({
variables: {
content: `Item ${suffixIndex + 1}`,
},
});
}, [createTodo]);
const toggleTodosTableVisible = useCallback(() => {
setTodosTableVisible((prevState) => !prevState);
}, []);
return (
<div>
<button type="button" onClick={handleCreateButtonClick}>
Create Todo Item
</button>
<button type="button" onClick={toggleTodosTableVisible}>
Toggle TodosTable Visible
</button>
{todosTableVisible && <TodosTable />}
</div>
);
}
const Provider = () => (
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
export default Provider;
进入全屏模式 退出全屏模式
让我们一步一步看看它是如何工作的。
1\。按下切换按钮
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--Xv8b26DL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev -to-uploads.s3.amazonaws.com/uploads/articles/g1g4j5j6b1396ga6lygl.png)
2\。按两次创建按钮
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--MLa77viV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/tus8kyu1al54yrqgrtyp.png)
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--s7-Vzbto--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev -to-uploads.s3.amazonaws.com/uploads/articles/k1ldy96s02k00zf7f010.png)
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--TPcS2Szi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to -uploads.s3.amazonaws.com/uploads/articles/2rc4mj50c4862o13k8af.png)
您可以在网络选项卡中查看创建的数据。
3\。按两次切换按钮(用于重新安装组件)
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--93cYeGSD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev- to-uploads.s3.amazonaws.com/uploads/articles/3dd74p8gb5634rq2ds07.png)
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--BzuLdKAa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to -uploads.s3.amazonaws.com/uploads/articles/30yjdzu7dbo21iqw2d2z.png)
空桌子还在,对吧?网络选项卡中甚至没有其他请求。
4\。重新加载选项卡并切换表格
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--4B0xcRBH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/48b81gjjq4xp8qf32vjf.png)
现在,您可以看到表格。让我解释一下。
在第一次请求时,客户端从服务器获得一个空数组,并将数据存储在缓存中。
我重新安装了表(第 3 步),它在缓存中找到了空数组,这就是表仍然为空的原因。
重新加载后,它们会显示来自服务器的数据,因为缓存已经消失。
仅缓存
它只使用缓存。如果没有缓存数据,则会引发错误。
我重写了测试这个选项的代码。
function TodosTable() {
const {
data: todosData,
loading: todosLoading,
error,
} = useQuery(GET_TODOS, {
fetchPolicy: "cache-only",
});
if (todosLoading) return <span>Loading...</span>;
console.log({ todosData, todosLoading, error });
if (error) {
return <h1>Error: {error}</h1>;
}
return (
<table>
<thead>
<tr>
<th>id</th>
<th>content</th>
<th>checked</th>
</tr>
</thead>
<tbody>
{todosData?.getTodos.map((todo) => (
<tr key={todo.id}>
<td>{todo.id}</td>
<td>{todo.content}</td>
<td>{todo.checked}</td>
</tr>
))}
</tbody>
</table>
);
}
function App() {
const [fetchTodos] = useLazyQuery(GET_TODOS);
const [createTodo] = useMutation(CREATE_TODO);
const [todosTableVisible, setTodosTableVisible] = useState(false);
const handleFetchTodos = useCallback(() => {
fetchTodos();
}, [fetchTodos]);
const handleCreateButtonClick = useCallback(() => {
createTodo({
variables: {
content: `Item ${suffixIndex + 1}`,
},
});
}, [createTodo]);
const toggleTodosTableVisible = useCallback(() => {
setTodosTableVisible((prevState) => !prevState);
}, []);
return (
<div>
<button type="button" onClick={handleFetchTodos}>
Fetch Todos
</button>
<button type="button" onClick={handleCreateButtonClick}>
Create Todo Item
</button>
<button type="button" onClick={toggleTodosTableVisible}>
Toggle TodosTable Visible
</button>
{todosTableVisible && <TodosTable />}
</div>
);
}
进入全屏模式 退出全屏模式
1\。按下切换按钮
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--ov0i9YYD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to -uploads.s3.amazonaws.com/uploads/articles/py401zwyr4ixokefl232.png)
老实说,我没想到会有这样的结果。我认为这会引发错误,因为他们在文档中说A cache-only query throws an error if the cache does not contain data for all requested fields.。无论如何,让我们继续。
2\。重新加载并按下获取按钮。
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--wSsNVL4R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev -to-uploads.s3.amazonaws.com/uploads/articles/v5m7wobautmyv3rxwldu.png)
您可以在网络选项卡中查看响应数据。
3\。按下切换按钮。
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--ytb3L3-X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev- to-uploads.s3.amazonaws.com/uploads/articles/l92ey60zyk1tj1iv53qe.png)
现在,您可以看到数据。
4\。按下创建按钮,然后重新安装(按下切换按钮两次)表
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--CEMI4Oll--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/zsvw9eiz0h4enyng6hnj.png)
它仍然是一样的。如您所见,cache-only仅使用缓存数据。
如果您手动获取数据,它也会显示出来,但是如果您获取部分数据怎么办?它将如何显示?
让我们看看它是如何出现的。
const GET_TODOS2 = gql`
query {
getTodos {
id
checked
}
}
`;
const [fetchTodos] = useLazyQuery(GET_TODOS2);
进入全屏模式 退出全屏模式
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--iETRl3PS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/gmkyn1e5stc3h40qv4nr.png)
数据的显示取决于缓存中的数据。
对不起,我没有注意到有空列,所有数字都是2。我将部分代码从
<td>{todo.checked}</td>
...
const handleCreateButtonClick = useCallback(() => {
createTodo({
variables: {
content: `Item ${suffixIndex + 1}`,
},
});
}, [createTodo]);
进入全屏模式 退出全屏模式
至
<td>{todo.checked ? "checked" : "unchecked"}</td>
...
const handleCreateButtonClick = useCallback(() => {
createTodo({
variables: {
content: `Item ${suffixIndex}`,
},
});
suffixIndex++;
}, [createTodo]);
进入全屏模式 退出全屏模式
缓存和网络
使用此策略,它首先使用缓存中的数据并发出请求。该请求会自动更新数据。
对于这个测试,我删除了在 TodosTable 中呈现加载文本的代码。
function TodosTable() {
const {
data: todosData,
error,
} = useQuery(GET_TODOS, {
fetchPolicy: "cache-and-network",
});
if (error) {
return <h1>Error: {error}</h1>;
}
return (
<table>
<thead>
<tr>
<th>id</th>
<th>content</th>
<th>checked</th>
</tr>
</thead>
<tbody>
{todosData?.getTodos.map((todo) => (
<tr key={todo.id}>
<td>{todo.id}</td>
<td>{todo.content}</td>
<td>{todo.checked ? "checked" : "unchecked"}</td>
</tr>
))}
</tbody>
</table>
);
}
进入全屏模式 退出全屏模式
加载时,组件将使用缓存中的数据。
由于我们以我们的互联网速度生活在未来,我们将无法识别。所以让我们先把网速降到3G再开始测试。
1\。创建两个项目并按下切换按钮
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--zheLWsEx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to -uploads.s3.amazonaws.com/uploads/articles/19g96k22a0jpc9984y7j.png)
2\。创建两个项目并重新安装表
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--Lk9jl0f_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/vue1mwzbc5qupy08xigk.gif)
它从缓存中显示开箱即用的数据,然后在获取完成后自动更新。
仅限网络
这使用来自服务器的数据,然后更新缓存。
1\。反复按下切换按钮
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--Hb_3iqwr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev- to-uploads.s3.amazonaws.com/uploads/articles/uxj5b9gymgdw52979ze3.gif)
它有一个延迟,直到请求返回。
对于下一个测试,无论network-only是否更新缓存,我将代码更改如下。
function TodosTable() {
const { data: todosData, error } = useQuery(GET_TODOS, {
fetchPolicy: "cache-only",
});
if (error) {
return <h1>Error: {error}</h1>;
}
return (
<table>
<thead>
<tr>
<th>id</th>
<th>content</th>
<th>checked</th>
</tr>
</thead>
<tbody>
{todosData?.getTodos.map((todo) => (
<tr key={todo.id}>
<td>{todo.id}</td>
<td>{todo.content}</td>
<td>{todo.checked ? "checked" : "unchecked"}</td>
</tr>
))}
</tbody>
</table>
);
}
function App() {
const [fetchTodos] = useLazyQuery(GET_TODOS, {
fetchPolicy: "network-only",
});
const [createTodo] = useMutation(CREATE_TODO);
const [todosTableVisible, setTodosTableVisible] = useState(false);
const handleFetchTodos = useCallback(() => {
fetchTodos();
}, [fetchTodos]);
const handleCreateButtonClick = useCallback(() => {
createTodo({
variables: {
content: `Item ${suffixIndex}`,
},
});
suffixIndex++;
}, [createTodo]);
const toggleTodosTableVisible = useCallback(() => {
setTodosTableVisible((prevState) => !prevState);
}, []);
return (
<div>
<button type="button" onClick={handleFetchTodos}>
Fetch Todos
</button>
<button type="button" onClick={handleCreateButtonClick}>
Create Todo Item
</button>
<button type="button" onClick={toggleTodosTableVisible}>
Toggle TodosTable Visible
</button>
{todosTableVisible && <TodosTable />}
</div>
);
}
进入全屏模式 退出全屏模式
1\。按获取按钮,然后按切换按钮
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--wzI6mJ-n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https:// dev-to-uploads.s3.amazonaws.com/uploads/articles/2d4y2bk421qco1hulwbh.png)
表格显示数据为cache-only,表示network-only更新了缓存。
无缓存
它类似于network-only但它不更新缓存。在上面的代码中,我更改了作为惰性查询选项的一行。
const [fetchTodos] = useLazyQuery(GET_TODOS, {
fetchPolicy: "no-cache",
});
进入全屏模式 退出全屏模式
- 按获取按钮,然后按切换按钮
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--kiiP69B0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/ezvu0ynjoai1bb210890.png)
cache-only表中没有显示任何内容,因为no-cache不会更新缓存。
更改默认获取策略
正如我已经提到的,useQuery和useLazyQuery的默认选项是cache-first。如果要更改默认提取策略,请使用defaultOptions。
const client = new ApolloClient({
uri: "http://localhost:3000/graphql",
cache: new InMemoryCache(),
defaultOptions: {
watchQuery: {
fetchPolicy: "cache-only",
errorPolicy: "ignore",
},
query: {
fetchPolicy: "network-only",
errorPolicy: "all",
},
mutate: {
errorPolicy: "all",
},
},
});
进入全屏模式 退出全屏模式
结论
关于 Apollo Client,我有很多事情需要了解。我不明白他们为什么使用cache作为默认值。这就是为什么我将我的项目的默认获取策略设置为no-cache。但是,我在使用no-cache时遇到了一些问题。其中之一是useQuery不使用defaultOptions。虽然问题在提交中解决了,但它似乎还有一些与no-cache相关的问题。我认为在需要时使用特定策略是可以的,但阿波罗缓存系统做的事情比我预期的要多(比如automatically updating and making a rendering、refetchQueries)。我认为cache可能是使用 apollo 客户端的关键,但我必须进一步了解它。我希望这篇文章能在某个时候对你有所帮助。感谢您阅读帖子。
更多推荐

所有评论(0)