React-HOOK GraphQL Apollo——手拿把掐满满干货

丌官运诚
2023-12-01

react apollo_2020年的React + Apollo教程(真实示例)

简介

最近在写关于GraphQL的项目,所以搜到了一遍文章

这是我在中搜到的一遍文章,点此

是英文的,属实是大佬,写的挺好的,所以翻译了一哈

目录

入门

  • 什么是阿波罗,我们为什么需要它?
  • Apollo客户端设置
  • 创建一个新的Apollo客户端
  • 向客户端提供React组件
  • 直接使用客户端
  • 使用gql在.js文件中编写GraphQL

核心React挂钩 Apollo

  • useQuery Hook
  • useLazyQuery Hook
  • useMutation Hook
  • useSubscription Hook

必须的秘方 (Essential Recipes)

(翻译是基本食谱,我思考半天还是觉得必须的秘方比较合适)

  • 手动设置获取策略
  • 发生突变时更新缓存
  • 使用useQuery重新查询
  • 使用useMutation重新查询
  • 使用useApolloClient访问客户端

入门

What is Apollo and why do we need it? 什么是阿波罗,我们为什么需要它?

原文

Apollo is a library(库) that brings together two incredibly useful technologies used to build web and mobile apps—React and GraphQL.

React was made for creating great user experiences with JavaScript. GraphQL is a very straightforward and declarative new language to more easily and efficiently fetch and change data, whether it is from a database or even from static files.

Apollo is the glue that binds these two tools together, plus makes working with them a lot easier by giving us a lot of custom React hooks and features that enable us to both write GraphQL operations and JavaScript files and execute them with JavaScript code.

We’ll cover these features in depth throughout the course of this guide.

中文

Apollo是一个库,汇集了用于构建Web和移动应用程序的两种极其有用的技术:React和GraphQL。

React旨在通过JavaScript创造出色的用户体验。GraphQL是一种非常直接和声明性的新语言,可以更轻松有效地获取和更改数据,无论是从数据库还是从静态文件中获取和更改数据。

而Apollo是将这两个工具结合在一起的粘合剂,另外,它为我们提供了许多自定义的React钩子和功能,使我们能够编写GraphQL操作并使用JavaScript代码执行他们,从而使React和GraphQL的使用变得更加容易

我们将在本指南的过程中深入介绍这些功能。

Apollo Client basic setup / Apollo客户端设置

英文

If you are starting a project with a React template like Create React App, you will need to install the following as your base dependencies to get up and running with Apollo Client.

// with npm:
npm i @apollo/react-hooks apollo-boost graphql
 
// with yarn:
yarn add @apollo/react-hooks apollo-boost graphql

@apollo/react-hooks gives us React hooks that make performing our operations and working with Apollo client better

apollo-boost helps us set up the client along with parse our GraphQL operations

graphql also takes care of parsing the GraphQL operations (along with gql)

中文

如果您要用如 Create React App 之类的React模板启动项目,则需要安装一下内容作为基本依赖项,以启动并运行Apollo Client。

// with npm:
npm i @apollo/react-hooks apollo-boost graphql
 
// with yarn:
yarn add @apollo/react-hooks apollo-boost graphql

@apollo/react-hooks :为我们提供了 React hooks,使我们可以更好地执行操作并与Apollo客户端一起使用

apollo-boost :帮助我们设置客户端以及解析GraphQL操作

graphql :负责解析GraphQL操作(以及gql)

Apollo Client + subscriptions setup / Apollo客户端+订阅设置

英文

To use all manner of GraphQL operations (queries, mutations, and subscriptions), we need to install more specific dependencies as compared to just apollo-boost

// with npm:
npm i @apollo/react-hooks apollo-client graphql graphql-tag apollo-cache-inmemory apollo-link-ws
 
// with yarn:
yarn add @apollo/react-hooks apollo-client graphql graphql-tag apollo-cache-inmemory apollo-link-ws

apollo-client gives us the client directly, instead of from apollo-boost
graphql-tag is integrated into apollo-boost, but not included in apollo-client

apollo-cache-inmemory is needed to setup our own cache (which apollo-boost, in comparison, does automatically)

apollo-link-ws is needed for communicating over websockets, which subscriptions require

中文

要使用所有形式的GraphQL操作(查询、突变、和订阅),与apollo-boost相比我们需要安装更具体的依赖项

// with npm:
npm i @apollo/react-hooks apollo-client graphql graphql-tag apollo-cache-inmemory apollo-link-ws
 
// with yarn:
yarn add @apollo/react-hooks apollo-client graphql graphql-tag apollo-cache-inmemory apollo-link-ws

apollo-client :直接向我们提供客户,而不是来自apollo-boost
graphql-tag :已集成到Apollo-Boost中,但不包括在Apollo-Client

apollo-cache-inmemory : 来设置我们自己的缓存(相比之下, apollo-boost会自动执行)

apollo-link-ws : 通过网络套接字进行通信需要apollo-link-ws ,订阅需要

Creating a new Apollo Client (basic setup) / 创建一个新的Apollo客户端(基本设置)

英文

The most straightforward setup for creating an Apollo client is by instantiating a new client and providing just the uri property, which will be your GraphQL endpoin。

import ApolloClient from "apollo-boost";
const client = new ApolloClient({
uri: "https://your-graphql-endpoint.com/api/graphql"
});

apollo-boost was developed in order to make doing things like creating an Apollo Client as easy as possible. What it lacks for the time being, however, is support for GraphQL subscriptions over a websocket connection.

By default, it performs the operations over an http connection (as you can see through our provided uri above).

In short, use apollo-boost to create your client if you only need to execute queries and mutations in your app.

It setups an in-memory cache by default, which is helpful for storing our app data locally. We can read from and write to our cache to prevent having to execute our queries after our data is updated. We’ll cover how to do that a bit later.

中文

创建Apollo客户端最直接的是指是实例化一个新客户端并仅提供uri属性,该属性将成为您的GraphQL端点。

import ApolloClient from "apollo-boost";
const client = new ApolloClient({
uri: "https://your-graphql-endpoint.com/api/graphql"
});

开发apollo-boost是为了使诸如Apollo客户端程序之类的工作尽可能轻松。但是,目前暂时缺乏通过Websocket连接支持GraphQL订阅的功能。

默认情况下,同通过http连接执行操作(如您在上面提供的uri中所见)

简而言之,如果只需要在应用程序中执行查询和变异,请使用 apollo-boost 创建客户端

默认情况下,它会设置内存缓存,这有助于在本地存储我们的应用数据。我们可以读取和写入缓存,以防止在数据更新后不得不执行查询。下面我们会介绍如何执行该操作

Creating a new Apollo Client (+ subscriptions setup) / 创建一个新的Apollo Client(+订阅设置)

英文

Subscriptions are useful for more easily displaying the result of data changes (through mutations) in our app.

Generally speaking, we use subscriptions as an improved kind of query. Subscriptions use a websocket connection to ‘subscribe’ to updates and data, enabling new or updated data to be immediately displayed to our users without having to reexecute queries or update the cache.

import ApolloClient from "apollo-client";
import { WebSocketLink } from "apollo-link-ws";
import { InMemoryCache } from "apollo-cache-inmemory";
 
const client = new ApolloClient({
  link: new WebSocketLink({
    uri: "wss://your-graphql-endpoint.com/v1/graphql",
    options: {
      reconnect: true,
      connectionParams: {
        headers: {
          Authorization: "Bearer yourauthtoken",
        },
      },
    },
  }),
  cache: new InMemoryCache(),
});

中文

订阅有助于在我们的应用中更轻松地显示数据更改的结果(通过突变)

一般来说,我们使用订阅作为一种改进的查询。订阅使用websocke连接来“订阅”更新数据,使新的或更新的数据能立即显示给我们用户,而无需重新执行查询或更新缓存。

import ApolloClient from "apollo-client";
import { WebSocketLink } from "apollo-link-ws";
import { InMemoryCache } from "apollo-cache-inmemory";
 
const client = new ApolloClient({
  link: new WebSocketLink({
    uri: "wss://your-graphql-endpoint.com/v1/graphql",
    options: {
      reconnect: true,
      connectionParams: {
        headers: {
          Authorization: "Bearer yourauthtoken",
        },
      },
    },
  }),
  cache: new InMemoryCache(),
});

Providing the client to React components / 向客户端提供React组件

英文

After creating a new client, passing it to all components is essential in order to be able to use it within our components to perform all of the available GraphQL operations.

The client is provided to the entire component tree using React Context, but instead of creating our own context, we import a special context provider from @apollo/react-hooks called ApolloProvider . We can see how it differs from the regular React Context due to it having a special prop, client , specifically made to accept the created client.

Note that all of this setup should be done in your index.js or App.js file (wherever your Routes declared) so that the Provider can be wrapped around all of your components.

import { ApolloProvider } from "@apollo/react-hooks";
 
const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <BrowserRouter>
        <Switch>
          <Route exact path="/" component={App} />
          <Route exact path="/new" component={NewPost} />
          <Route exact path="/edit/:id" component={EditPost} />
        </Switch>
      </BrowserRouter>
    </ApolloProvider>
  </React.StrictMode>,
  rootElement
);

中文

创建新客户端后,将其传递给所有组件是必不可少的,以便能够在我们的组件中使用它来执行所有可用的GraphQL操作。

使用React Contex将客户端提供给整个组件树,但是我们没有创建自己的环境,而是从@apollo/react-hooks导入了一个特殊的环境提供给程序ApolloProvider。我们可以看到它是如何不同于常规的背景下做出React,由于其特殊的道具,它client,专门用来接收创建的客户端

请注意,所有这些设置都应在index.js或App.js文件(无论你声明路由的地方在哪)中完成,以便Provider可以包装在所有组件周围

import { ApolloProvider } from "@apollo/react-hooks";
 
const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <BrowserRouter>
        <Switch>
          <Route exact path="/" component={App} />
          <Route exact path="/new" component={NewPost} />
          <Route exact path="/edit/:id" component={EditPost} />
        </Switch>
      </BrowserRouter>
    </ApolloProvider>
  </React.StrictMode>,
  rootElement
);

Using the client directly / 直接使用客户端

英文

The Apollo client is most important part of the library due to the fact that it is responsible for executing all of the GraphQL operations that we want to perform with React.

We can use the created client directly to perform any operation we like. It has methods corresponding to queries ( client.query() ), mutations ( client.mutate() ), and subscriptions ( client.subscribe() ).

Each method accepts an object and it’s own corresponding properties:

// executing queries
client
  .query({
    query: GET_POSTS,
    variables: { limit: 5 },
  })
  .then((response) => console.log(response.data))
  .catch((err) => console.error(err));
 
// executing mutations
client
  .mutate({
    mutation: CREATE_POST,
    variables: { title: "Hello", body: "World" },
  })
  .then((response) => console.log(response.data))
  .catch((err) => console.error(err));
 
// executing subscriptions
client
  .subscribe({
    subscription: GET_POST,
    variables: { id: "8883346c-6dc3-4753-95da-0cc0df750721" },
  })
  .then((response) => console.log(response.data))
  .catch((err) => console.error(err));

Using the client directly can be a bit tricky, however, since in making a request, it returns a promise. To resolve each promise, we either need .then() and .catch() callbacks as above or to await each promise within a function declared with the async keyword.

中文

Apollo客户端是该库中最重要的部分,因为它负责执行我们要使用React执行的所有GraphQL操作。

我们可以直接使用创建的客户端执行我们喜欢的任何操作。它有着查询方法( client.query() )、突变( client.mutate() ), 和订阅( client.subscribe() ).

每个方法都接受一个对象,并且具有自己的相对属性:

// executing queries
client
  .query({
    query: GET_POSTS,
    variables: { limit: 5 },
  })
  .then((response) => console.log(response.data))
  .catch((err) => console.error(err));
 
// executing mutations
client
  .mutate({
    mutation: CREATE_POST,
    variables: { title: "Hello", body: "World" },
  })
  .then((response) => console.log(response.data))
  .catch((err) => console.error(err));
 
// executing subscriptions
client
  .subscribe({
    subscription: GET_POST,
    variables: { id: "8883346c-6dc3-4753-95da-0cc0df750721" },
  })
  .then((response) => console.log(response.data))
  .catch((err) => console.error(err));

直接使用客户端可能会有些棘手,但是,因为在发出请求时,他会返回承诺。要解决每个诺言,我们要么如上所述需要.then().catch()回调,要么在用async await

Writing GraphQL operations in .js files (gql) / 在.js文件(gql)中编写GraphQL操作

英文

Notice above that I didn’t specify the contents of the variables GET_POSTS , CREATE_POST , and GET_POST .

They are the operations written in the GraphQL syntax which specify how to perform the query, mutation, and subscription respectively. They are what we would write in any GraphiQL console to get and change data.

The issue here, however, is that we can’t write and execute GraphQL instructions in JavaScript (.js) files, like our React code has to live in.

To parse the GraphQL operations, we use a special function called a tagged template literal to allow us to express them as JavaScript strings. This function is named gql .

// if using apollo-boost
import { gql } from "apollo-boost";
// else, you can use a dedicated package graphql-tag
import gql from "graphql-tag";
 
// query
const GET_POSTS = gql`
  query GetPosts($limit: Int) {
    posts(limit: $limit) {
      id
      body
      title
      createdAt
    }
  }
`;
 
// mutation
const CREATE_POST = gql`
  mutation CreatePost($title: String!, $body: String!) {
    insert_posts(objects: { title: $title, body: $body }) {
      affected_rows
    }
  }
`;
 
// subscription
const GET_POST = gql`
  subscription GetPost($id: uuid!) {
    posts(where: { id: { _eq: $id } }) {
      id
      body
      title
      createdAt
    }
  }
`;

中文:

注意,上面我没有指定变量 GET_POSTS , CREATE_POST , 和GET_POST

它们是用GraphQL语法编写的操作,分别指定如何执行查询,更改和订阅。它们时我们将任何GraphiQL控制台中编写的用于获取和更改数据的内容

但是,这里的问题是,我们无法在JavaScript(.js)文件中编写和执行GraphQL指令,就像我们的React代码必须存在一样

为了解析GraphQL操作,我们使用一个称为标记模板文字的特殊函数,使我们可以将它们表示为JavaScript字符串。函数名为gql

// if using apollo-boost
import { gql } from "apollo-boost";
// else, you can use a dedicated package graphql-tag
import gql from "graphql-tag";
 
// query
const GET_POSTS = gql`
  query GetPosts($limit: Int) {
    posts(limit: $limit) {
      id
      body
      title
      createdAt
    }
  }
`;
 
// mutation
const CREATE_POST = gql`
  mutation CreatePost($title: String!, $body: String!) {
    insert_posts(objects: { title: $title, body: $body }) {
      affected_rows
    }
  }
`;
 
// subscription
const GET_POST = gql`
  subscription GetPost($id: uuid!) {
    posts(where: { id: { _eq: $id } }) {
      id
      body
      title
      createdAt
    }
  }
`;

核心 React Hooks Apollo

useQuery Hook

英文

The useQuery hook is arguably the most convenient way of performing a GraphQL query, considering that it doesn’t return a promise that needs to be resolved.

It is called at the top of any function component (as all hooks should be) and receives as a first required argument—a query parsed with gql .

It is best used when you have queries that should be executed immediately, when a component is rendered, such as a list of data which the user would want to see immediately when the page loads.

useQuery returns an object from which we can easily destructure the values that we need. Upon executing a query, there are three primary values will need to use within every component in which we fetch data. They are loading , error , and data .

const GET_POSTS = gql`
  query GetPosts($limit: Int) {
    posts(limit: $limit) {
      id
      body
      title
      createdAt
    }
  }
`;
 
function App() {
  const { loading, error, data } = useQuery(GET_POSTS, {
    variables: { limit: 5 },
  });
 
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error!</div>;
 
  return data.posts.map((post) => <Post key={post.id} post={post} />);
}

Before we can display the data that we’re fetching, we need to handle when we’re loading (when loading is set to true) and we are attempting to fetch the data.

At that point, we display a div with the text ‘Loading’ or a loading spinner. We also need to handle the possibility that there is an error in fetching our query, such as if there’s a network error or if we made a mistake in writing our query (syntax error).

Once we’re done loading and there’s no error, we can use our data in our component, usually to display to our users (as we are in the example above).

There are other values which we can destructure from the object that useQuery returns, but you’ll need loading, error, and data in virtually every component where you execute useQuery. You can see a full list of all of the data we can get back from useQuery here.

中文

考虑到它不会返回需要解决的promise回调,因此useQuery hook可以说是执行GraphQL查询的最方便的方法

它在函数组件的开头(想其他钩子一样)被调用,并作为第一个必须的参数接收——用gql解析查询

当您使用组件时具有应立即执行的查询时最好使用它,例如用户希望在页面加载时立即查看的数据列表

useQuery返回一个对象,我们可以从中轻松地结构所需的值。执行查询后,需要在其中获取数据的每个组件中使用三个主要值。他们是loading , error , 和data

const GET_POSTS = gql`
  query GetPosts($limit: Int) {
    posts(limit: $limit) {
      id
      body
      title
      createdAt
    }
  }
`;
 
function App() {
  const { loading, error, data } = useQuery(GET_POSTS, {
    variables: { limit: 5 },
  });
 
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error!</div>;
 
  return data.posts.map((post) => <Post key={post.id} post={post} />);
}

在显示正在获取的数据之前,我们需要在加载时进行处理(当loading设置为true时),并且我们尝试获取数据

届时,我们将显示一个带有文本“Loading”的div或一个加载中的图标。我们还需要处理在获取查询时出错的可能性,例如是否存在网络错误或编写查询时出错(语法错误)

一旦完成加载并且没有错误,我们就可以在组件中使用我们的数据,通常将其显示给我们的用户(如上例所示)

我们还可以从useQuery返回的对象中解构出其他值,但是实际上在执行useQuery每个组件中都需要loading , error , 和data。您可以在此处看到我们可以从useQuery中获取的所有数据的完整列表。

useLazyQuery Hook

英文

The useLazyQuery hook provides another way to perform a query, which is intended to be executed at some time after the component is rendered or in response to a given data change.

useLazyQuery is very useful for things that happen at any unknown point of time, such as in response to a user’s search operation.

function Search() {
  const [query, setQuery] = React.useState("");
  const [searchPosts, { data }] = useLazyQuery(SEARCH_POSTS, {
    variables: { query: `%${query}%` },
  });
  const [results, setResults] = React.useState([]);
 
  React.useEffect(() => {
    if (!query) return;
    // function for executing query doesn't return a promise
    searchPosts();
    if (data) {
      setResults(data.posts);
    }
  }, [query, data, searchPosts]);
 
  if (called && loading) return <div>Loading...</div>;
 
  return results.map((result) => (
    <SearchResult key={result.id} result={result} />
  ));
}

useLazyQuery differs from useQuery , first of all, in what’s returned from the hook. It returns an array which we can destructure, instead of an object.

Since we want to perform this query sometime after the component is mounted, the first element that we can destructure is a function which you can call to perform that query when you choose. This query function is named searchPosts in the example above.

The second destructured value in the array is an object, which we can use object destructuring on and from which we can get all of the same properties as we did from useQuery , such as loading , error , and data .

We also get an important property named called , which tells us if we’ve actually called this function to perform our query. In that case, if called is true and loading is true, we want to return “Loading…” instead of our actual data, because are waiting for the data to be React + Apollo Client 2020 Cheatsheet 9 returned. This is how useLazyQuery handles fetching data in a synchronous way without any promises.

Note that we again pass any required variables for the query operation as a property, variables, to the second argument. However, if we need, we can pass those variables on an object provided to the query function itself.

中文

useLazyQuery hook 提供了另一种执行查询的方法,该方法旨在在呈现组件之后或响应给定的数据更改后的某个时间执行。

useLazyQuery 对于在任何未知时间点发生的事情(例如响应用户的搜索操作)非常有用

function Search() {
  const [query, setQuery] = React.useState("");
  const [searchPosts, { data }] = useLazyQuery(SEARCH_POSTS, {
    variables: { query: `%${query}%` },
  });
  const [results, setResults] = React.useState([]);
 
  React.useEffect(() => {
    if (!query) return;
    // function for executing query doesn't return a promise
    searchPosts();
    if (data) {
      setResults(data.posts);
    }
  }, [query, data, searchPosts]);
 
  if (called && loading) return <div>Loading...</div>;
 
  return results.map((result) => (
    <SearchResult key={result.id} result={result} />
  ));
}

useLazyQuery 不同于useQuery ,首先,在钩子返回的内容中。它返回一个我们可以解构的数组,而不是对象

由于我们哟啊在组件安装后的某个使劲按执行此查询,因此我们可以分解的第一个元素是一个函数,您可以选择该函数时调用该函数来执行该查询。在上面的示例中,此查询功能名为searchPosts

数组中的第二个重构值是一个对象,我们可以在上面使用对象解构,并从中获得与useQuery相同的所有属性,例如loadingerrordata

我们还获得命名的一个重要性质called ,它告诉我们,如果我们实际上是调用这个函数来执行我们的查询。 在这种情况下,如果called为true并且loading为true,则我们想返回“ Loading …”而不是我们的实际数据,因为正在等待数据被返回。 这就是useLazyQuery如何以同步方式处理数据获取而没有任何promises的方式。

请注意,我们再次将查询操作所需的所有变量作为属性(变量)传递给第二个参数。 但是,如果需要,我们可以将这些变量传递给提供给查询函数本身的对象。

useMutation Hook

英文

Now that we know how to execute lazy queries, we know exactly how to work with the useMutation hook.

Like the useLazyQuery hook, it returns an array which we can destructure into its two elements. In the first element, we get back a function, which in this case, we can call it to perform our mutation operation. For next element, we can again destructure an object which returns to us loading, error and data.

import { useMutation } from "@apollo/react-hooks";
import { gql } from "apollo-boost";
 
const CREATE_POST = gql`
  mutation CreatePost($title: String!, $body: String!) {
    insert_posts(objects: { body: $body, title: $title }) {
      affected_rows
    }
  }
`;
 
function NewPost() {
  const [title, setTitle] = React.useState("");
  const [body, setBody] = React.useState("");
  const [createPost, { loading, error }] = useMutation(CREATE_POST);
 
  function handleCreatePost(event) {
    event.preventDefault();
    // the mutate function also doesn't return a promise
    createPost({ variables: { title, body } });
  }
 
  return (
    <div>
      <h1>New Post</h2>
      <form onSubmit={handleCreatePost}>
        <input onChange={(event) => setTitle(event.target.value)} />
        <textarea onChange={(event) => setBody(event.target.value)} />
        <button disabled={loading} type="submit">
          Submit
        </button>
        {error && <p>{error.message}</p>}
      </form>
    </div>
  );
}

Unlike with queries, however, we don’t use loading or error in order to conditionally render something. We generally use loading in such situations as when we’re submitting a form to prevent it being submitted multiple times, to avoid executing the same mutation needlessly (as you can see in the example above).

We use error to display what goes wrong with our mutation to our users. If for example, some required values to our mutation are not provided, we can easily use that error data to conditionally render an error message within the page so the user can hopefully fix what’s going wrong.

As compared to passing variables to the second argument of useMutation, we can access a couple of useful callbacks when certain things take place, such as when the mutation is completed and when there is an error. These callbacks are named onCompleted and onError.

The onCompleted callback gives us access to the returned mutation data and it’s very helpful to do something when the mutation is done, such as going to a different page. The onError callback gives us the returned error when there is a problem with the mutation and gives us other patterns for handling our errors.

const [createPost, { loading, error }] = useMutation(CREATE_POST, {
  onCompleted: (data) => console.log("Data from mutation", data),
  onError: (error) => console.error("Error creating a post", error),
});

中文

既然我们知道了如何执行懒加载的查询,那么就恰恰知道了如何使用useMutation hook

就像useLazyQuery hook 一样,它返回一个数组,我们可以将其分解为两个元素。在第一个元素中,我们返回一个函数,在这种情况下,我们可以调用它执行我们的突变操作。对于下一个元素,我们可以再次分解一个对象该对象返回给我们loadingerrordata

import { useMutation } from "@apollo/react-hooks";
import { gql } from "apollo-boost";
 
const CREATE_POST = gql`
  mutation CreatePost($title: String!, $body: String!) {
    insert_posts(objects: { body: $body, title: $title }) {
      affected_rows
    }
  }
`;
 
function NewPost() {
  const [title, setTitle] = React.useState("");
  const [body, setBody] = React.useState("");
  const [createPost, { loading, error }] = useMutation(CREATE_POST);
 
  function handleCreatePost(event) {
    event.preventDefault();
    // the mutate function also doesn't return a promise
    createPost({ variables: { title, body } });
  }
 
  return (
    <div>
      <h1>New Post</h2>
      <form onSubmit={handleCreatePost}>
        <input onChange={(event) => setTitle(event.target.value)} />
        <textarea onChange={(event) => setBody(event.target.value)} />
        <button disabled={loading} type="submit">
          Submit
        </button>
        {error && <p>{error.message}</p>}
      </form>
    </div>
  );
}

但是,与查询不同,我们不使用loadingerror来有条件地渲染某些内容。 我们通常在诸如提交表单的情况下使用loading ,以防止多次提交表单,以避免不必要地执行相同的更改(如您在上面的示例中看到的)。

我们使用error向用户显示我们的突变出了什么问题。 例如,如果未提供突变所需的某些值,则我们可以轻松地使用该错误数据在页面中有条件地呈现错误消息,以便用户希望可以解决问题。

与将变量传递给useMutation的第二个参数useMutation ,当某些事情发生时,例如当突变完成和出现错误时,我们可以访问几个有用的回调。 这些回调分别命名为onCompletedonError

onCompleted回调使我们可以访问返回的突变数据,并且在完成突变后执行某些操作(例如转到另一个页面)非常有帮助。 当突变存在问题时, onError回调为我们提供了返回的错误,并为我们提供了处理错误的其他模式。

const [createPost, { loading, error }] = useMutation(CREATE_POST, {
  onCompleted: (data) => console.log("Data from mutation", data),
  onError: (error) => console.error("Error creating a post", error),
});

useSubscription Hook

英文

The useSubscription hook works just like the useQuery hook.

useSubscription returns an object that we can destructure, that includes the same properties, loading, data, and error.

It executes our subscription immediately when the component is rendered. This means we need to handle loading and error states, and only afterwards display/use our data.

import { useSubscription } from "@apollo/react-hooks";
import gql from "graphql-tag";
 
const GET_POST = gql`
  subscription GetPost($id: uuid!) {
    posts(where: { id: { _eq: $id } }) {
      id
      body
      title
      createdAt
    }
  }
`;
 
// where id comes from route params -> /post/:id
function PostPage({ id }) {
  const { loading, error, data } = useSubscription(GET_POST, {
    variables: { id },
    // shouldResubscribe: true (default: false)
    // onSubscriptionData: data => console.log('new data', data)
    // fetchPolicy: 'network-only' (default: 'cache-first')
  });
 
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error!</div>;
 
  const post = data.posts[0];
 
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </div>
  );
}

Just like useQuery, useLazyQuery and useMutation, useSubscription accepts variables as a property provided on the second argument.

It also accepts, however, some useful properties such as shouldResubscribe. This is a boolean value, which will allow our subscription to automatically resubscribe, when our props change. This is useful for when we’re passing variables to our you subscription hub props that we know will change.

Additionally, we have a callback function called onSubscriptionData, which enables us to call a function whenever the subscription hook receives new data. Finally, we can set the fetchPolicy, which defaults to ‘cache-first’.

中文

useSubscription hook的工作原理与useQuery hook相同。

useSubscription返回一个可以分解的对象,其中包括相同的属性,loadingerrordata

呈现组件后,它将立即执行我们的订阅。 这意味着我们需要处理加载和错误状态,然后才显示/使用我们的数据。

import { useSubscription } from "@apollo/react-hooks";
import gql from "graphql-tag";
 
const GET_POST = gql`
  subscription GetPost($id: uuid!) {
    posts(where: { id: { _eq: $id } }) {
      id
      body
      title
      createdAt
    }
  }
`;
 
// where id comes from route params -> /post/:id
function PostPage({ id }) {
  const { loading, error, data } = useSubscription(GET_POST, {
    variables: { id },
    // shouldResubscribe: true (default: false)
    // onSubscriptionData: data => console.log('new data', data)
    // fetchPolicy: 'network-only' (default: 'cache-first')
  });
 
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error!</div>;
 
  const post = data.posts[0];
 
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </div>
  );
}

就像useQuery,useLazyQuery和useMutation一样,useSubscription接受variables作为第二个参数提供的属性。

但是,它也接受一些有用的属性,例如shouldResubscribe 。 这是一个布尔值,当我们的道具更改时,这将允许我们的订阅自动重新订阅。 当我们将变量传递给我们知道会改变的您的订阅中心道具时,这很有用。

此外,我们还有一个名为onSubscriptionData的回调函数,该函数使我们可以在订阅挂钩接收到新数据时调用一个函数。 最后,我们可以设置fetchPolicy ,默认为’cache-first’。

必须的秘方

Manually Setting the Fetch Policy / 手动设置提取策略

英文

What can be very useful about Apollo is that it comes with its own cache, which it uses to manage the data that we query from our GraphQL endpoint.

Sometimes, however, we find that due to this cache, things aren’t updated in the UI in the way that we want.

In many cases we don’t, as in the example below, where we are editing a post on the edit page, and then after editing our post, we navigate to the home page to see it in a list of all posts, but we see the old data instead:

// route: /edit/:postId
function EditPost({ id }) {
  const { loading, data } = useQuery(GET_POST, { variables: { id } });
  const [title, setTitle] = React.useState(loading ? data?.posts[0].title : "");
  const [body, setBody] = React.useState(loading ? data?.posts[0].body : "");
  const [updatePost] = useMutation(UPDATE_POST, {
    // after updating the post, we go to the home page
    onCompleted: () => history.push("/"),
  });
 
  function handleUpdatePost(event) {
    event.preventDefault();
    updatePost({ variables: { title, body, id } });
  }
 
  return (
    <form onSubmit={handleUpdatePost}>
      <input
        onChange={(event) => setTitle(event.target.value)}
        defaultValue={title}
      />
      <input
        onChange={(event) => setBody(event.target.value)}
        defaultValue={body}
      />
      <button type="submit">Submit</button>
    </form>
  );
}
 
// route: / (homepage)
function App() {
  const { loading, error, data } = useQuery(GET_POSTS, {
    variables: { limit: 5 },
  });
 
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error!</div>;
 
  // updated post not displayed, still see old data
  return data.posts.map((post) => <Post key={post.id} post={post} />);
}

This not only due to the Apollo cache, but also the instructions for what data the query should fetch. We can changed how the query is fetched by using the fetchPolicy property.

By default, the fetchPolicy is set to ‘cache-first’. It’s going to try to look at the cache to get our data instead of getting it from the network.

An easy way to fix this problem of not seeing new data is to change the fetch policy. However, this approach is not ideal from a performance standpoint, because it requires making an additional request (using the cache directly does not, because it is local data).

There are many different options for the fetch policy listed below:

{
  fetchPolicy: "cache-first"; // default
  /* 
    cache-and-network
    cache-first
    cache-only
    network-only
    no-cache
    standby
  */
}

I won’t go into what each policy does exactly, but to solve our immediate problem, if you always want a query to get the latest data by requesting it from the network, we set fetchPolicy to ‘network-first’.

const { loading, error, data } = useQuery(GET_POSTS, {
  variables: { limit: 5 },
  fetchPolicy: "network-first"
});

中文

关于Apollo的非常有用的是,它带有自己的缓存,用于管理从GraphQL端点查询的数据。

但是,有时候,我们发现由于缓存的原因,UI中的内容并未按照我们想要的方式进行更新。

在许多情况下,我们不会像下面的示例那样在编辑页面上编辑帖子,然后在编辑帖子之后,导航到主页以在所有帖子列表中查看它,但是我们查看旧数据:

// route: /edit/:postId
function EditPost({ id }) {
  const { loading, data } = useQuery(GET_POST, { variables: { id } });
  const [title, setTitle] = React.useState(loading ? data?.posts[0].title : "");
  const [body, setBody] = React.useState(loading ? data?.posts[0].body : "");
  const [updatePost] = useMutation(UPDATE_POST, {
    // after updating the post, we go to the home page
    onCompleted: () => history.push("/"),
  });
 
  function handleUpdatePost(event) {
    event.preventDefault();
    updatePost({ variables: { title, body, id } });
  }
 
  return (
    <form onSubmit={handleUpdatePost}>
      <input
        onChange={(event) => setTitle(event.target.value)}
        defaultValue={title}
      />
      <input
        onChange={(event) => setBody(event.target.value)}
        defaultValue={body}
      />
      <button type="submit">Submit</button>
    </form>
  );
}
 
// route: / (homepage)
function App() {
  const { loading, error, data } = useQuery(GET_POSTS, {
    variables: { limit: 5 },
  });
 
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error!</div>;
 
  // updated post not displayed, still see old data
  return data.posts.map((post) => <Post key={post.id} post={post} />);
}

这不仅是由于Apollo缓存,而且还取决于查询应获取哪些数据的说明。 我们可以使用fetchPolicy属性更改查询的获取方式。

默认情况下, fetchPolicy设置为“缓存优先”。 它将尝试查看缓存以获取我们的数据,而不是从网络获取数据。

解决此问题的简便方法是更改获取策略。 但是,从性能的角度来看,此方法并不理想,因为它需要发出额外的请求(因为它是本地数据,所以不需要直接使用缓存)。

下面列出了许多用于提取策略的选项:

{
  fetchPolicy: "cache-first"; // default
  /* 
    cache-and-network
    cache-first
    cache-only
    network-only
    no-cache
    standby
  */
}

我不会深入研究每个策略的确切作用,但是要解决我们眼前的问题,如果您始终希望查询通过从网络请求来获取最新数据,请将fetchPolicy设置为"network-first"。

const { loading, error, data } = useQuery(GET_POSTS, {
  variables: { limit: 5 },
  fetchPolicy: "network-first"
});

Updating the cache upon a mutation / 发生突变时更新缓存

英文

Instead of bypassing the cache by changing the fetch policy of useQuery, let’s attempt to fix this problem by manually updating the cache.

When performing a mutation with useMutation. We have access to another callback, known as update.

update gives us direct access to the cache as well as the data that is returned from a successful mutation. This enables us to read a given query from the cache, take that new data and write the new data to the query, which will then update what the user sees.

Working with the cache manually is a tricky process that a lot of people tend to avoid, but it’s very helpful because it saves some time and resources by not having to perform the same request multiple times to update the cache manually.

function EditPost({ id }) {
  const [updatePost] = useMutation(UPDATE_POST, {
    update: (cache, data) => {
      const { posts } = cache.readQuery(GET_POSTS);
      const newPost = data.update_posts.returning;
      const updatedPosts = posts.map((post) =>
        post.id === id ? newPost : post
      );
      cache.writeQuery({ query: GET_POSTS, data: { posts: updatedPosts } });
    },
    onCompleted: () => history.push("/"),
  });
 
  // ...
}

We first want to read the query and get the previous data from it. Then we need to take the new data. In this case, to find the post with a given id and replace it with newPost data, otherwise have it be the previous data, and then write that data back to the same query, making sure that it has the same data structure as before.

After all this, whenever we edit a post and are navigated back to the home page, we should see that new post data.

中文

让我们尝试通过手动更新缓存来解决此问题,而不是通过更改useQuery的获取策略来useQuery缓存。

使用useMutation执行突变时。 我们可以访问另一个回调,称为update

update使我们可以直接访问缓存以及成功进行突变后返回的数据。 这使我们能够从缓存中读取给定的查询,获取新数据并将新数据写入查询,然后更新用户看到的内容。

手动处理缓存是一个棘手的过程,很多人倾向于避免这一过程,但这非常有帮助,因为它无需多次执行同一请求来手动更新缓存,从而节省了时间和资源。

function EditPost({ id }) {
  const [updatePost] = useMutation(UPDATE_POST, {
    update: (cache, data) => {
      const { posts } = cache.readQuery(GET_POSTS);
      const newPost = data.update_posts.returning;
      const updatedPosts = posts.map((post) =>
        post.id === id ? newPost : post
      );
      cache.writeQuery({ query: GET_POSTS, data: { posts: updatedPosts } });
    },
    onCompleted: () => history.push("/"),
  });
 
  // ...
}

我们首先要读取查询并从中获取以前的数据。 然后,我们需要获取新数据。 在这种情况下,要查找具有给定id的帖子并将其替换为newPost数据,否则将其作为先前的数据,然后将该数据写回到相同的查询中,并确保它具有与以前相同的数据结构。

毕竟,无论何时我们编辑帖子并导航回主页,我们都应该看到新的帖子数据。

Refetching queries with useQuery / 使用useQuery重新查询

英文

Let’s say we display a list of posts using a GET_POSTS query and are deleting one of them with a DELETE_POST mutation.

When a user deletes a post, what do we want to happen?

Naturally, we want it to be removed from the list, both the data and what is displayed to the users. When a mutation is performed, however, the query doesn’t know that the data is changed.

There are a few ways of updating what we see, but one approach is to reexecute the query.

We can do so by grabbing the refetch function which we can destructure from the object returned by the useQuery hook and pass it down to the mutation to be executed when it is completed, using the onCompleted callback function:

function Posts() {
  const { loading, data, refetch } = useQuery(GET_POSTS);
 
  if (loading) return <div>Loading...</div>;
 
  return data.posts.map((post) => (
    <Post key={post.id} post={post} refetch={refetch} />
  ));
}
 
function Post({ post, refetch }) {
  const [deletePost] = useMutation(DELETE_POST, {
    onCompleted: () => refetch(),
  });
 
  function handleDeletePost(id) {
    if (window.confirm("Are you sure you want to delete this post?")) {
      deletePost({ variables: { id } });
    }
  }
 
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
      <button onClick={() => handleDeletePost(post.id)}>Delete</button>
    </div>
  );
}

中文

假设我们使用GET_POSTS查询显示帖子列表,并删除其中一个带有DELETE_POST突变的DELETE_POST

当用户删除帖子时,我们想发生什么?

自然,我们希望将其从列表中删除,包括数据和显示给用户的内容。 但是,当执行变异时,查询不知道数据已更改。

有几种方法可以更新我们看到的内容,但是一种方法是重新执行查询。

我们可以抓住这样做refetch功能,我们可以通过返回的对象解构useQuery钩传下来的突变完成时,它要执行,使用onCompleted回调函数:

function Posts() {
  const { loading, data, refetch } = useQuery(GET_POSTS);
 
  if (loading) return <div>Loading...</div>;
 
  return data.posts.map((post) => (
    <Post key={post.id} post={post} refetch={refetch} />
  ));
}
 
function Post({ post, refetch }) {
  const [deletePost] = useMutation(DELETE_POST, {
    onCompleted: () => refetch(),
  });
 
  function handleDeletePost(id) {
    if (window.confirm("Are you sure you want to delete this post?")) {
      deletePost({ variables: { id } });
    }
  }
 
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
      <button onClick={() => handleDeletePost(post.id)}>Delete</button>
    </div>
  );
}

Refetching Queries with useMutation / 使用useMutation重新查询

英文

Note that we can also utilize the useMutation hook to reexecute our queries through an argument provided to the mutate function, called refetchQueries.

It accepts an array of queries that we want to refetch after a mutation is performed. Each queries is provided within an object, just like we would provide it to client.query(), and consists of a query property and a variables property.

Here is a minimal example to refetch our GET_POSTS query after a new post is created:

function NewPost() {
  const [createPost] = useMutation(CREATE_POST, {
    refetchQueries: [
      { 
        query: GET_POSTS, 
        variables: { limit: 5 } 
      }
    ],
  });
 
  // ...
}

中文

请注意,我们还可以利用 useMutation hook 来通过提供给突变函数的参数来执行查询,称为refetchqueries

它接受执行突变后要重新获取的查询数组。 每个查询都在一个对象内提供,就像我们将其提供给client.query()一样,并且由一个查询属性和一个变量属性组成。

这是创建新帖子后重新获取GET_POSTS查询的最小示例:

function NewPost() {
  const [createPost] = useMutation(CREATE_POST, {
    refetchQueries: [
      { 
        query: GET_POSTS, 
        variables: { limit: 5 } 
      }
    ],
  });
 
  // ...
}

Using the client with useApolloClient / 将客户端与useApolloClient一起使用

英文

We can get access to the client across our components with the help of a special hook called use Apollo client. This execute the hook at the top of our function component and we get back the client itself.

function Logout() {
  const client = useApolloClient();
  // client is the same as what we created with new ApolloClient()
 
  function handleLogout() {
    // handle logging out user, then clear stored data
    logoutUser();
    client.resetStore().then(() => console.log("logged out!"));
    /* Be aware that .resetStore() is async */
  }
 
  return <button onClick={handleLogout}>Logout</button>;
}

And from there we can execute all the same queries, mutations, and subscriptions.

Note that there are a ton more features that come with methods that come with the client. Using the client, we can also write and read data to and from the cache that Apollo sets up (using client.readData() and client.writeData()).

Working with the Apollo cache deserves its own crash course in itself. A great benefit of working with Apollo is that we can also use it as a state management system to replace solutions like Redux for our global state. If you want to learn more about using Apollo to manage global app state you can check out the following link.

I attempted to make this cheatsheet as comprehensive as possible, though it still leaves out many Apollo features that are worth investigating.

If you want to more about Apollo, be sure to check out the official Apollo documentation.

中文

借助名为use Apollo client的特殊钩子,我们可以跨组件访问客户端。 这将执行我们函数组件顶部的挂钩,然后我们将返回客户端本身。

function Logout() {
  const client = useApolloClient();
  // client is the same as what we created with new ApolloClient()
 
  function handleLogout() {
    // handle logging out user, then clear stored data
    logoutUser();
    client.resetStore().then(() => console.log("logged out!"));
    /* Be aware that .resetStore() is async */
  }
 
  return <button onClick={handleLogout}>Logout</button>;
}

从那里我们可以执行所有相同的查询,突变和订阅。

请注意,客户端附带的方法还提供许多其他功能。 使用客户端,我们还可以使用Apollo设置的缓存向其中写入数据和从中读取数据(使用client.readData()client.writeData() )。

使用Apollo缓存本身应该拥有自己的崩溃过程。 与Apollo合作的一大好处是,我们还可以将其用作状态管理系统,以代替Redux等解决方案来解决我们的全球状态。 如果要了解有关使用Apollo管理全局应用程序状态的更多信息,可以查看以下链接

我尝试使该备忘单尽可能全面,尽管它仍然遗漏了许多值得研究的Apollo功能。

如果您想进一步了解Apollo,请务必查看Apollo官方文档

 类似资料: