这些天我一直在做一个 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 文档

我将向您展示每个获取策略是如何工作的。

缓存优先

这是一个默认的获取策略,如果缓存中有数据则使用缓存,否则从服务器获取数据。

我为此测试编写了代码。有两个按钮。一个用于创建待办事项,另一个用于显示或隐藏数据表(mountunmount)。数据表获取数据为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)

[A request payload](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",
  });

进入全屏模式 退出全屏模式

  1. 按获取按钮,然后按切换按钮

[空数据](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不会更新缓存。

更改默认获取策略

正如我已经提到的,useQueryuseLazyQuery的默认选项是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 renderingrefetchQueries)。我认为cache可能是使用 apollo 客户端的关键,但我必须进一步了解它。我希望这篇文章能在某个时候对你有所帮助。感谢您阅读帖子。

Logo

React社区为您提供最前沿的新闻资讯和知识内容

更多推荐