10. 查询真实数据
2m

概览

我们准备好了,但它们还不知道如何协同工作。

是我们之前构建的所有元素(架构、)完美协调在一起的位置。

在本节中,我们将

  • 将我们的 的所有部分汇总在一起:架构、
  • 更新我们的代码生成配置文件,以包含我们服务器的

更新我们的服务器

src/index.ts中,我们现在可以告诉我们的服务器所有有关我们的 的信息。

让我们在顶部导入我们的 resolversListingAPI

index.ts
import { resolvers } from "./resolvers";
import { ListingAPI } from "./datasources/listing-api";

我们将更新 ApolloServer选项,以除了 typeDefs之外,还接受 resolvers

index.ts
const server = new ApolloServer({
typeDefs,
resolvers,
});

为了将我们的服务器与我们的 ListingAPI连接起来,我们将跳转到 startStandaloneServer函数。此函数获取第二个 ,它是一个用于配置服务器选项的对象。

index.ts
const { url } = await startStandaloneServer(server, {
// TODO: configure server options
});

我们将在其中定义一个 context函数,该函数返回一个所有我们的 将共享的对象:contextValue(即我们之前讨论的第三位置

我们将 context属性设置为一个异步函数,该函数返回一个对象。

index.ts
const { url } = await startStandaloneServer(server, {
context: async () => {
// this object becomes our resolver's contextValue, the third positional argument
return {
// TODO
};
},
});

请记住,我们想要访问 dataSources.listingAPI(及其方法),该方法来自 contextValue resolver 的参数。因此,我们返回一个允许我们执行此操作的对象!

我们将在对象内设置一个 dataSources属性,该属性设置为另一个对象。此对象将具有一个 listingAPI键(小写),该键返回 ListingAPI数据源(我们之前导入),的一个实例。

index.ts
const { url } = await startStandaloneServer(server, {
context: async () => {
return {
dataSources: {
listingAPI: new ListingAPI(),
},
};
},
});

我们的 函数期望在 dataSources.listingAPI中找到 contextValue,这就是我们在这里定义了一个名为 dataSources的属性,其中包含另一个名为 listingAPI的属性的原因。属性 dataSources命名不是必需的——我们选择 dataSources作为一种约定。您可以给此属性指定您想要的任何名称,但请确保更新 函数以访问同一属性。

还有一件事!为了利用 RESTDataSource的缓存功能,我们需要将服务器的缓存传递到我们的 ListingAPI实例中。

在我们返回 contextValue对象之前,我们先对 cache属性进行解构,该属性属于 server。然后,我们将一个包含该 cache属性的对象传递到 ListingAPI类。

index.ts
const { url } = await startStandaloneServer(server, {
context: async () => {
const { cache } = server;
return {
dataSources: {
listingAPI: new ListingAPI({ cache }),
},
};
},
});

若要了解 ApolloServer可以接收的更多选项,查看文档

更新 codegen

从技术角度讲,现在我们服务器的所有部件就位了!我们在服务器上实例化了,它负责将数据源传递给我们的函数作为contextValue,即它们 的第三个位置参数。

只有一个小小的问题:如果我们返回resolvers.ts并悬停在我们的featuredListings dataSources参数上,我们会看到它仍然具有隐式类型any

resolvers.ts
(parameter) dataSources: any

让我们在types.ts中的Resolvers类型再次进行审查。在此处,我们可以看到它实际接纳了一个名为ContextType的类型参数。无论我们将ContextType定义成什么,都会被我们的函数接收。

types.ts
export type Resolvers<ContextType = any> = {
Listing?: ListingResolvers<ContextType>;
Query?: QueryResolvers<ContextType>;
};

此参数表示我们在服务器的context属性中设置数据的类型。它使我们能够更准确地描述我们的函数在其第三个位置参数contextValue上可以访问哪种类型的数据;我们可以确定它们可以调用哪些方法,以及这些方法将返回哪种类型的数据。

我们知道在服务器的contextListingAPI实例)中设置的数据类型,但我们还没有告知 TypeScript,我们来为我们的类提供一些更多信息,以便它在编码时对我们有所帮助。

src目录中,我们将创建一个名为context.ts的新文件。我们将在该文件中定义描述传递给服务器的上下文的类型。

📂 src
┣ 📂 datasources
┣ 📄 context.ts
┣ 📄 graphql.d.ts
┣ 📄 helpers.ts
┣ 📄 index.ts
┣ 📄 resolvers.ts
┣ 📄 schema.graphql
┗ 📄 types.ts

在内部,我们将定义和导出一个名为 DataSourceContext 的类型。在 DataSourceContext 中,我们将定义一个名为 dataSources 的属性,它是一个对象。

context.ts
export type DataSourceContext = {
dataSources: {};
};

我们希望给 dataSources 对象与我们在服务器上设置的相同属性,因此我们可以添加 listingAPI 键以及 ListingAPI 类。确保也要导入此类!

context.ts
import { ListingAPI } from "./datasources/listing-api";
export type DataSourceContext = {
dataSources: {
listingAPI: ListingAPI;
};
};

注意:我们不需要在此处实例化 ListingAPI 类,这就足以让我们的类型定义获取有关方法和属性的信息,这些属性和方法可用于 类。

更新 codegen.ts

定义了我们的 DataSourceContext 类型后,我们可以更新我们的 codegen.ts 文件以对其进行考虑。

就在 plugins 键的下方,我们可以添加一个新的 config 属性。这是一个指定 contextType 的对象。

codegen.ts
const config: CodegenConfig = {
schema: "./src/schema.graphql",
generates: {
"./src/types.ts": {
plugins: ["typescript", "typescript-resolvers"],
config: {
contextType:
},
},
},
};

作为 contextType 的值,我们将传递文件路径到我们的 context.ts 文件,该路径相对于 ./src/types.ts 文件。我们的 context.ts 文件位于同一个 src 文件夹中,因此我们的路径是 "./context"。最后,要指向我们在文件中定义的类型,我们可以将 #DataSourceContext 添加到文件路径末尾。

codegen.ts
config: {
contextType: "./context#DataSourceContext",
},

注意:确保在指定的文件路径中 ./context#DataSourceContext 之间没有一个正斜杠 (/)。

最后,让我们再次运行我们的 codegen 命令!

npm run generate

现在,当我们重新打开 types.ts 文件并向下滚动到我们的 Resolvers 类型时,我们将看到 ContextType 默认为 DataSourceContext

types.ts
export type Resolvers<ContextType = DataSourceContext> = {
Listing?: ListingResolvers<ContextType>;
Query?: QueryResolvers<ContextType>;
};

回到 resolvers.ts 中,我们可以悬停在 dataSources 上,以查看我们的类型是否正确推断。它是一个带有 listingAPI 属性的对象!

resolvers.ts
(parameter) dataSources: {
listingAPI: ListingAPI;
}

(这意味着如果我们尝试使用我们的 没有 访问权限的方法,它将显示一个告诉我们的错误!)

查询数据

当我们的服务器仍在运行时,让我们回到 https://127.0.0.1:4000 上的 Explorer。

打开 Settings 面板,并确保已将 Mock Responses 切换到关闭位置。

https://127.0.0.1:4000

The Explorer open to the Settings panel, where Mock Responses is highlighted and the toggle is set to OFF

让我们再次尝试相同的

query FeaturedListings {
featuredListings {
id
title
numOfBeds
costPerNight
closedForBookings
}
}

现在... 我们有了 真实的数据! 🎉

练习

为什么我们需要使用 DataSourceContext 为我们的 codegen 配置文件提供数据?

要点

  • 我们可以使用 context属性来定义 我们的 需要访问这些数据源。
  • 代码生成器允许我们指定我们的 访问的上下文类型,以便我们在整个项目中受益于类型安全性。

下一步

太棒了!我们将 REST API 响应转换为我们在模式中定义的类型,并且我们拥有无缝的 体验。但现在,我们的房源列表非常有限。根据我们的模型,我们需要开始探索一些其他功能——例如查询附加的房源属性(你好,便利设施!)或询问 特定房源。让我们在下一课中提升我们的 API。

前一个

分享你对这一课的问题和评论

本课程目前正在

beta 版
.你的反馈有助于我们改进!如果你卡住了或感到困惑,请告诉我们,我们会帮助你。所有评论都是公开的,并且必须遵循 Apollo 行为准则。请注意,已经解决或处理的评论可能会被删除。

你将需要一个 GitHub 帐户才能在下面进行帖子。没有吗? 在我们的 Odyssey 论坛中发帖。