跳至主要内容
版本: v18.0.0

GraphQL 和 Relay

本节是对 Relay 与 GraphQL、React 和堆栈其他部分的关系的概述。不用担心理解每个细节,只需尝试了解要点,然后继续下一节开始使用代码。当我们完成整个教程中的工作示例时,将解释更多详细信息。


GraphQL 是一种用于查询和修改服务器数据的语言。GraphQL 的独特之处在于,它不像具有固定 API 端点那样,而是提供了一个选项集,客户端可以使用这些选项集请求任何它可能需要的组合数据。这使前端开发人员能够更快地移动,因为随着数据需求的改变,无需编写和部署新的端点。这也意味着,当发布新版本的客户端时,它可以只请求它需要的数据,而不会留下额外的字段以与旧版本兼容。

GraphQL 为跨任何类型的后端查询数据提供了一个统一的接口。无论您的数据是在关系 SQL 数据库中、图形数据库中还是微服务舰队中,GraphQL 服务器都可以从多个后端收集数据并将其发送到客户端以进行单个响应,这比从客户端向每个服务发出单独的查询更有效。

在传统的 HTTP API 中,每个 URL 都会返回一组固定信息

Request:
GET /person?id=24601

Response:
{"id": "24601", "name": "Jean Valjean", "age": 64, "occupation": "Mayor"}

在 GraphQL 中,客户端请求它想要的信息,服务器只返回请求的信息

Request:
query {
person(id: "24601") {
name
occupation
}
}

Response:
{
"person": {
"name": "Jean Valjean",
"occupation": "Mayor"
}
}

请注意,响应中只包含客户端请求的特定字段。

顾名思义,GraphQL 将数据组织成一个 *图形*。该图包含 *节点*(例如对象或记录)和 *边*(从一个节点到另一个节点的指针)

Nodes with fields connected by edges

GraphQL 允许您从一个节点到另一个节点跟踪这些边,并询问您访问的每个节点的信息。例如,这里我们从一个人到他们的城市,并获取关于该城市的信息

query {
person(id: "24601") {
name
occupation
location {
name
population
}
}
}

Query diagram

这意味着我们可以检索关于一个对象全景的信息,这些信息都在一个查询中 - 换句话说,您可以有效地获得一个屏幕的所有数据,而无需发送一个接一个的多个请求。但是,您无需为 UI 中的每个屏幕编写和维护一个单独的端点。

相反,您的 GraphQL 服务器提供一个 *模式*,该模式描述了有哪些类型的节点、它们是如何连接的以及每个节点包含哪些信息。然后,您可以从该模式中挑选和选择,以选择您想要的信息。

本教程中的示例应用程序是一个新闻提要应用程序,因此它的模式由以下类型组成:

  • Story,它代表新闻提要故事 - 它具有诸如标题、图片和指向发布该故事的人或组织的 *边* 之类的字段
  • Person,包含诸如姓名、电子邮件和朋友列表(指向其他 Person 的边)之类的信息。
  • Viewer,它代表查看应用程序的人,并包含诸如他们的新闻提要故事列表之类的信息
  • Image,它具有图片本身的 URL 以及 alt 文本描述。

GraphQL 语言包含一个类型系统和用于指定模式的语言。以下是我们的示例应用程序的模式定义中的代码片段 - 不要担心每个细节,它只是为了让您了解一般情况

// A newsfeed story. It has fields, some of which are scalars (e.g. strings
// and numbers) and some that are edges that point to other nodes in the graph,
// such as the 'thumbnail' and 'poster' fields:
type Story {
id: ID!
category: Category
title: String
summary: String
thumbnail: Image
poster: Actor
}

// An Actor is an entity that can do something on the site. This is an
// interface that multiple different types can implement, in this case
// Person and Organization:
interface Actor {
id: ID!
name: String
profilePicture: Image
}

// This is a specific type that implements that interface:
type Person implements Actor {
id: ID!
name: String
email: String
profilePicture: Image
location: Location
}

// The schema also lets you define enums, such as the category
// of a newsfeed story:
enum Category {
EDUCATION
NEWS
COOKING
}

除了查询之外,GraphQL 还允许您发送 *变异*,这些变异要求服务器更新其数据。如果查询类似于 HTTP GET 请求,则变异类似于 POST 请求。像 POST 一样,它们允许服务器使用更新后的数据进行响应。GraphQL 还具有 *订阅*,这些订阅允许建立开放连接以进行实时更新。

(GraphQL 通常在 HTTP 上实现,因此查询和变异不仅 *类似于* GET 和 POST,而且也可以作为 GET 和 POST 发送。)


现在我们已经讨论了 GraphQL,让我们来谈谈 Relay。它有几个不同的部分和组件,我们将在深入研究代码之前简要介绍一下。

Relay 是一个面向 GraphQL 的客户端数据管理库,但它以一种非常特殊的方式使用 GraphQL,从 GraphQL 中获得了最大益处。

为了获得最佳性能,您希望您的应用程序在每个屏幕或页面开始时发出一个请求,而不是让各个组件发出自己的请求。但问题是,这会将组件和屏幕耦合在一起,从而产生一个巨大的维护问题:如果您需要在特定组件中添加一些额外数据,则必须找到使用该组件的每个屏幕,并将新字段添加到该屏幕的查询中。另一方面,如果您不再需要特定字段,则必须再次从每个查询中删除该字段 - 但这一次,您是否确定该字段没有被其他 *组件* 使用?维护这些大型屏幕范围的查询变得非常困难。

Relay 的独特优势是,它允许每个组件在本地声明自己的数据需求,然后将这些需求缝合在一起形成更大的查询,从而避免了这种权衡。这样一来,您既获得了性能,又获得了可维护性。

Relay 使用一个 *编译器* 来扫描您的 JavaScript 代码以查找 GraphQL 片段,然后将这些片段缝合在一起形成完整的查询。

The Relay Compiler combines fragments into a query

除了编译器之外,Relay 还拥有运行时代码,用于管理 GraphQL 的获取和处理。它维护已检索到的所有数据的本地缓存(称为 *存储*),并将属于每个组件的数据分配给该组件

The Relay Runtime fetches the query and vends out the appropriate data to each component according to its fragment

拥有一个集中存储的优势在于,它可以让您在数据更新时保持数据一致性。例如,如果您的 UI 允许某人编辑他们的姓名,那么您可以在一个地方进行更新,并且每个显示该人姓名的组件都会看到新的信息,即使它们位于不同的屏幕上,并且最初使用不同的查询检索数据。这是因为 Relay 在数据传入时 *规范化* 数据,这意味着它将看到的所有关于单个图形节点的数据合并到一个地方,因此它不会有多个相同节点的副本。

事实上,Relay 不仅仅是查询数据,它还提供了查询和更新的整个生命周期,包括对乐观更新和回滚的支持。您可以进行分页、刷新数据 - 您创建 UI 所需的所有基本操作。每当存储中的数据更新时,Relay 都会高效地仅重新渲染正在显示该特定数据的组件。

摘要

GraphQL 是一种将数据建模为图形并从服务器查询该数据(以及更新该数据)的语言。Relay 是一个基于 React 的 GraphQL 客户端库,它允许您从与每个 React 组件位于同一位置的单个片段构建查询。一旦数据被查询,Relay 就会保持一致性,并在数据更新时重新渲染组件。