查询基础
此部分中的内容
- 我们将使用一个 React 组件,它显示硬编码的占位符数据,并对其进行修改,以便它使用 GraphQL 查询来获取数据。
- 我们将学习如何使用 Relay 从 GraphQL 生成的 TypeScript 类型来确保类型安全。
使用 Relay,您可以使用 GraphQL 查询来获取数据。查询指定了您的应用程序要获取的 GraphQL 图的一部分,从某个根节点开始,并从节点到节点遍历以检索以树状结构形式的特定数据。
现在,我们的示例应用程序不会获取任何数据,它只是呈现一些硬编码到 React 组件中的占位符数据。让我们修改它,使其使用 Relay 获取一些数据。
打开名为 Newsfeed.tsx
的文件。(教程中的所有组件都在 src/components
中。)您应该在其中看到一个 <Newsfeed>
组件,其中数据是硬编码的
export default function Newsfeed() {
const story = {
title: "Placeholder Story",
summary:
"Placeholder data, to be replaced with data fetched via GraphQL",
poster: {
name: "Placeholder Person",
profilePicture: {
url: "/assets/cat_avatar.png",
},
},
thumbnail: {
url: "/assets/placeholder.jpeg",
},
};
return (
<div className="newsfeed">
<Story story={story} />
</div>
);
}
我们将用从服务器获取的数据替换此占位符数据。首先,我们需要定义一个 GraphQL 查询。在 Newsfeed 组件上方添加以下声明
import { graphql } from 'relay-runtime';
const NewsfeedQuery = graphql`
query NewsfeedQuery {
topStory {
title
summary
poster {
name
profilePicture {
url
}
}
thumbnail {
url
}
}
}
`;
让我们分解一下
- 要在 JavaScript 中嵌入 GraphQL,我们将使用一个字符串字面量,用
graphql``
标签标记。此标签允许 Relay 编译器在 JavaScript 代码库中找到并编译 GraphQL。 - 我们的 GraphQL 字符串包含一个查询声明,其中包含
query
关键字,然后是一个查询名称。请注意,查询名称必须以模块名称开头(在本例中为Newsfeed
)。 - 查询声明内部是字段,它指定了要查询的信息:
- 某些字段是标量字段,它检索字符串、数字或其他信息单元。
- 其他字段是边,它允许我们从图中的一个节点遍历到另一个节点。当一个字段是边时,它后面跟着另一个包含该边另一端节点的字段的代码块
{ }
。这里,poster
字段是一个从故事到发布它的人的边。一旦我们遍历到该人,我们就可以包含有关该人的字段,例如他们的name
。
这说明了该查询正在请求的图的一部分
现在我们已经定义了查询,我们需要做两件事。
- 运行 Relay 编译器,以便它了解新的 Graphql 查询。[npm run relay.]
- 修改我们的 React 组件,以便它获取查询,并使用服务器返回的数据。
如果您打开 package.json,您会发现 relay
脚本已连接到运行 Relay 编译器。这就是 npm run relay
的作用。一旦编译器成功更新/生成了新的编译查询,您将能够在 src/components/ 下的 generated 文件夹中以 NewsfeedQuery.graphql.ts 的形式找到它。此项目附带预先计算的片段,因此,除非您执行此步骤,否则您将无法获得所需的结果。
接下来,回到 Newsfeed
组件,并首先删除占位符数据。然后,用以下代码替换它
import { useLazyLoadQuery } from "react-relay";
export default function Newsfeed({}) {
const data = useLazyLoadQuery(
NewsfeedQuery,
{},
);
const story = data.topStory;
// As before:
return (
<div className="newsfeed">
<Story story={story} />
</div>
);
}
useLazyLoadQuery
钩子获取并返回数据。它有两个参数
- 我们之前定义的GraphQL 查询。
- 变量,它们将与查询一起传递到服务器。此查询没有声明任何变量,因此它是一个空对象。
useLazyLoadQuery
返回的对象具有与查询相同的形状。例如,如果以 JSON 格式打印,它可能看起来像这样
{
topStory: {
title: "Local Yak Named Yak of the Year",
summary: "The annual Yak of the Year awards ceremony ...",
poster: {
name: "Baller Bovine Board",
profilePicture: {
url: '/images/baller_bovine_board.jpg',
},
},
thumbnail: {
url: '/images/max_the_yak.jpg',
}
}
}
请注意,GraphQL 查询选择的每个字段都对应于 JSON 响应中的一个属性。
此时,您应该会看到从服务器获取的一个故事
服务器的响应被人工减缓,以使加载状态可见,这在我们在应用程序中添加更多交互性时会派上用场。如果您想删除延迟,请打开 server/index.js
并删除对 sleep()
的调用。
useLazyLoadQuery
钩子在组件首次呈现时获取数据。Relay 还提供用于在应用程序甚至加载之前预取数据的 API——这些 API 将在后面介绍。在任何情况下,Relay 都使用 Suspense 来显示加载指示器,直到数据可用。
这是 Relay 最基本的形式:在呈现组件时获取 GraphQL 查询的结果。随着教程的进行,我们将看到 Relay 的功能如何协同工作,使您的应用程序更易于维护——从了解 Relay 如何生成与每个查询相对应的 TypeScript 类型开始。
深入了解:用于数据加载的 Suspense
Suspense 是 React 中的一个新 API,它允许 React 在数据加载完成之前等待,然后再呈现需要该数据的组件。当组件需要在呈现之前加载数据时,React 会显示加载指示器。您可以使用一个名为 Suspense
的特殊组件来控制加载指示器的位置和样式。
现在,App.tsx
中有一个 Suspense
组件,它在 useLazyLoadQuery
加载数据时显示旋转器。
我们在后面的部分中详细介绍 Suspense,届时我们将向应用程序添加更多交互性。
深入了解:查询是静态的
Relay 应用程序中的所有 GraphQL 字符串都由 Relay 编译器预处理,并从生成的捆绑代码中删除。这意味着您无法在运行时构造 GraphQL 查询——它们必须是静态字符串字面量,以便在编译时已知。但这带来了重大优势。
首先,它允许 Relay 为查询的结果生成类型定义,使您的代码更具类型安全性。
其次,Relay 将 GraphQL 字符串字面量替换为告诉 Relay 该做什么的对象。这比在运行时直接使用 GraphQL 字符串快得多。
此外,可以将 Relay 的编译器配置为在构建应用程序时将查询保存到服务器,以便在运行时,客户端只需发送查询 ID 而不是查询本身。这可以节省捆绑包大小和网络带宽,并且可以防止攻击者编写恶意查询,因为只有应用程序构建时所使用的查询才需要可用。
因此,当您的程序中有一个 GraphQL 标记字符串字面量时...
const MyQuery = graphql`
query MyQuery {
viewer {
name
}
}
`;
... JavaScript 变量 MyQuery
实际上分配给一个类似于这样的对象
const MyQuery = {
kind: "query",
selections: [
{
name: "viewer",
kind: "LinkedField",
selections: [
name: "name",
kind: "ScalarField",
],
}
]
};
以及各种其他属性和信息。这些数据结构经过精心设计,允许 JIT 非常快地运行 Relay 的有效负载处理代码。如果您好奇,可以使用Relay 编译器浏览器 来尝试。
Relay 和类型系统
您可能会注意到,TypeScript 发现我们编写的此代码存在错误
const story = data.topStory;
^^^^^^^^
Property 'topStory' does not exist on type 'unknown'
要解决此问题,我们需要使用 Relay 生成的类型来标注对 useLazyLoadQuery
的调用。这样,TypeScript 就会根据我们在查询中选择的字段来了解 data
应该是什么类型。添加以下内容
import type {NewsfeedQuery as NewsfeedQueryType} from './__generated__/NewsfeedQuery.graphql';
function Newsfeed({}) {
const data = useLazyLoadQuery
<NewsfeedQueryType>
(NewsfeedQuery, {});
...
}
如果我们在 __generated__/NewsfeedQuery.graphql
中查看,我们会看到以下类型定义——通过我们刚刚添加的标注,TypeScript 知道 data
应该具有此类型
export type NewsfeedQuery$data = {
readonly topStory: {
readonly poster: {
readonly name: string | null;
readonly profilePicture: {
readonly url: string;
} | null;
};
readonly summary: string | null;
readonly thumbnail: {
readonly url: string;
} | null;
readonly title: string;
} | null;
};
Relay 编译器为应用程序中 graphql``
字面量中包含的每个 GraphQL 部分生成相应的 TypeScript 类型。只要 npm run dev
在运行,Relay 编译器就会在您保存 JavaScript 源文件时自动重新生成这些文件,因此您无需刷新任何内容来保持它们最新。
使用 Relay 生成的类型使您的应用程序更安全、更易于维护。除了 TypeScript,Relay 还支持 Flow 类型系统,如果您想使用它。使用 Flow 时,不需要在 useLazyLoadQuery
上进行额外的标注,因为 Flow 直接了解 graphql``
标记字面量的内容。
我们将在整个教程中重新讨论类型。但是接下来,我们将介绍 Relay 在可维护性方面帮助我们的另一种更重要的方法。
总结
查询是获取 GraphQL 数据的基础。我们已经看到
- 如何使用
graphql``
标记字面量在应用程序中定义 GraphQL 查询。 - 如何使用
useLazyLoadQuery
钩子在呈现组件时获取查询的结果。 - 如何导入 Relay 生成的类型以实现类型安全性。
在下一部分中,我们将介绍片段,这是 Relay 最核心和最具特色的方面之一。片段允许每个单独的组件定义自己的数据需求,同时保留了向服务器发出单个查询的性能优势。