Typescript
Overview
在本指南中,您可了解 MongoDB Node.js 驱动程序的 TypeScript 功能和限制。TypeScript 是一种可编译为 JavaScript 的强类型编程语言。
TypeScript 编译器提供实时类型检查。支持 TypeScript 的代码编辑器可以提供自动补全建议、以内联方式显示文档并识别与类型相关的错误。
该驱动程序的所有 TypeScript 功能都是可选的。使用该驱动程序编写的所有有效 JavaScript 代码也会是有效的 TypeScript 代码。
有关更多信息,请参阅 TypeScript 网站。
功能
如果您使用 TypeScript,则可以为驱动程序中的某些类指定一个类型。驱动程序中接受类型参数的所有类都具有默认类型 Document
。Document
接口具有以下定义:
interface Document { [key: string]: any; }
所有对象类型都会扩展 Document
接口。
有关对象类型的更多信息,请参阅 TypeScript 手册。
用于扩展文档的类型参数
以下类接受扩展 Document
接口的所有类型:
您可以传递扩展 Document
接口的类型参数,如下所示:
1 interface Pet { 2 name: string; 3 age: number; 4 } 5 6 const database = client.db("<your database>"); 7 const collection = database.collection<Pet>("<your collection>");
重要
不在类型参数中的键接收任何类型
指定类型参数中未列出的键会接收 any
类型。以下代码片段演示了此行为:
1 interface User { 2 email: string; 3 } 4 5 const database = client.db("<your database>"); 6 const myColl = db.collection<User>("<your collection>"); 7 myColl.find({ age: "Accepts any type!" });
任何类型的类型参数
以下类接受所有类型参数:
您可以在查找多个文档用法示例中找到一个代码片段,该片段展示了如何为FindCursor
类指定类型。
类型安全性和点符号
从版本 5.0 开始,默认情况下,Node.js 驱动程序不为搜索以点符号表示的字段的操作提供类型安全性。点符号是一种可用于导航嵌套 JSON 对象的语法。当您构造要传递给查询的过滤器时,即使您为以点符号表示的字段指定了错误的类型值,驱动程序也不会引发类型错误。
以下代码片段定义了包括一个 classification
字段的 ClassificationPet
接口,您可以通过该字段指定狗和猫的属名和颜色:
interface ClassificationPet { name: string; age: number; classification: { genus: "Canis" | "Felis"; color: string }; }
对于以下代码示例,驱动程序不会引发类型错误,即使 classification.color
的值是布尔值而不是字符串:
await myColl.findOneAndDelete({ "classification.color": false });
您可以将筛选器构造为 StrictFilter
或 StrictUpdateFilter
类型,以启用类型检查。
警告
StrictFilter
和 StrictUpdateFilter
类型是实验性的,可能会在有效查询中错误地显示类型错误。
在以下代码示例中,为该筛选器分配了 StrictFilter
类型。对于此筛选器类型,Node.js 驱动程序会报告类型错误,因为 classification.color
的值为布尔值而不是字符串。
const filterPredicate: StrictFilter<ClassificationPet> = { "classification.color": false }; await myColl.findOneAndDelete(filterPredicate);
以下示例会将 StrictUpdateFilter
类型分配给更新筛选器。Node.js 驱动程序会报告类型错误,因为 classification.color
的值为布尔值而不是字符串。
const updateFilter: StrictUpdateFilter<ClassificationPet> = { $set: { "classification.color": false } } await pets.updateOne({}, updateFilter);
引用包含变量的键
要查询集合或使用包含变量的键执行其他操作,您必须在指定键时使用 as const
断言。如果输入类型正确,此机制允许您的代码成功编译。
以下代码片段定义了 ClassificationPet
接口和 Mealtime
接口。ClassificationPet
包括一个 mealtimes
字段,而该字段包含一组 Mealtime
接口,且每个接口均包括一个 time
字段:
interface ClassificationPet { name: string; mealtimes: Mealtime[]; } interface Mealtime{ time: string; amount: number; }
以下代码片段对 ClassificationPet
文档集合执行查找和更新操作。该操作会更新索引 1
处 Mealtime
实例的嵌套 time
字段。索引位置由变量 mealCounter
指定:
const mealCounter = 1; await myColl.findOneAndUpdate( { name: "Lassie" }, { $set: { [`mealtimes.${mealCounter}.time` as const]: '04:00 PM' } }, );
要了解有关点表示法的更多信息,请参阅 MongoDB 手册中的点表示法。
要详细了解 Node.js 驱动程序中点表示法的限制,请参阅递归类型和点表示法部分。
使用 _id 字段
MongoDB 不建议将 _id
指定为模型的一部分。省略 _id
字段会使模型更加通用和可重用,并且可以更准确地模拟对应用程序重要的数据。Node 驱动程序的 TypeScript 集成负责将 _id
字段添加到相关方法的返回类型。
以下各节提供 _id
字段的写入和读取操作的相关信息。
插入操作和 _id 字段
您在传递给 Collection
实例的类型参数中指定 _id
字段的方式会影响插入操作的行为。下表描述了不同的 _id
字段规范如何影响插入操作:
_id 字段类型 | 示例类型 | 插入时是否为必填项 | 插入时的行为 |
---|---|---|---|
Unspecified | Not applicable | No | The driver creates an
ObjectId
value for each inserted document. |
Specified | { _id: number }; | Yes | If you do not specify a value for the _id field in an insert operation,
the driver raises an error. |
Specified as optional | { _id?: number }; | No | If you do not specify the _id field in an insert operation,
the driver adds an _id field value generated by the
primary key factory. |
如果您必须在定义的类型中指定 _id
字段以表示集合中的文档,但不想在插入操作中指定 _id
字段的值,则在创建集合时使用 OptionalId
辅助程序类型。OptionalId
类型接受类型形参作为参数,并返回带有可选_id
字段的类型。
以下代码段定义了 IdPet
接口,其中包括 _id
字段的类型:
interface IdPet { _id: ObjectId; name: string; age: number; }
以下代码使用前面的接口以及 OptionalId
类型来插入文档,无需指定 _id
字段的值:
const database = client.db("<your database>"); const collection = db.collection<OptionalId<IdPet>>("<your collection>"); myColl.insertOne({ name: "Spot", age: 2 });
要了解有关_id
字段的更多信息,请参阅 MongoDB 手册中的 _id 字段。
要了解有关本节中讨论的类型、接口和类的更多信息,请参阅以下资源:
OptionalId API 文档
PkFactory API 文档
ObjectId 源代码
查找方法和 _id 字段
Collection
类的 find
和 findOne
方法在其返回类型中包含 _id
字段。驱动程序根据您传递给 Collection
实例的类型参数推断返回的 _id
字段的类型。
如果传递给 Collection
实例的类型参数在其模式中包含 _id
字段,驱动程序则会推断从该方法返回的 _id
字段属于模式中指定的类型。
但是,如果传递给Collection
实例的类型参数在其架构中不包含 _id
字段,则驱动程序会推断从该方法返回的 _id
字段的类型为 ObjectId
。
提示
传递给 Collection
的类型参数仅影响从该方法返回的字段的类型推断。驱动程序不会将该字段转换为指定类型。类型参数模式中每个字段的类型必须与集合中相应字段的类型匹配。
以下代码使用 Pet 接口返回一个文档,该文档中 _id
的类型被推断为 ObjectId
:
const database = client.db("<your database>"); const collection = db.collection<Pet>("<your collection>"); const document = await myColl.findOne({ name: "Spot", }); const id : ObjectId = document._id;
以下代码使用 IdNumberPet
接口返回一个文档,其中 _id
被推断为 number
类型:
interface IdNumberPet { _id: number; name: string; age: number; } const database = client.db("<your database>"); const collection = db.collection<IdNumberPet>("<your collection>"); const document = await myColl.findOne({ name: "Spot", }); const id : number = document._id;
重要
投射
如果在 find 方法中指定投影,则必须向 find 方法传递一个反映投影文档结构的类型参数。如果没有类型参数,TypeScript 无法在编译时检查您是否安全地使用了投影文档。
下面的例子将形象说明此行为。以下代码片段将通过类型检查,但会在运行时引发错误:
const doc = await myColl.findOne( {}, { projection: { _id: 0, name: 1 } } ); console.log(doc._id.generationTime);
要在编译时捕获此错误,请将不包含 _id
字段的类型参数传递给 find 方法:
interface ProjectedDocument { name: string } const doc = await myColl.findOne<ProjectedDocument>( {}, { projection: { _id: 0, name: 1 } } ); // Compile time error: Property '_id' does not exist on type 'ProjectedDocument'. console.log(doc._id.generationTime);
要查看包含应用了投影的 find 方法的可运行 TypeScript 示例,请参阅查找文档页面。
要了解本节中讨论的类和方法的更多信息,请参阅以下 API 文档:
已知限制
了解 Node.js 驱动程序的以下 TypeScript 特定限制:
递归类型和点符号
Node.js 驱动程序无法在通过点符号引用的递归类型的嵌套实例中提供类型安全性。
递归类型是一种引用自身的类型。您可以允许宠物拥有自己的宠物,以便将 Pet 接口更新为递归类型。以下是递归类型的 Pet
接口:
interface RecursivePet { pet?: RecursivePet; name: string; age: number; }
注意
深度限制
当对使用点表示法的键进行类型检查时,Node.js 驱动程序不会遍历嵌套递归类型,以避免触及 TypeScript 的递归深度限制。
以下代码段使用点表示法引用了类型不正确的 RecursivePet 接口的嵌套实例,但是 TypeScript 编译器没有引发类型错误:
database .collection<RecursivePet>("<your collection>") .findOne({ "pet.age": "Spot" });
以下代码段引用类型不正确的 RecursivePet
接口的顶级实例,并引发类型错误:
database .collection<RecursivePet>("<your collection>") .findOne({ pet: "Spot" });
以上代码片段引发的错误如下:
index.ts(19,59): error TS2769: No overload matches this call. The last overload gave the following error. Type 'string' is not assignable to type 'Condition<Pet>'.
如果一定要在递归类型的嵌套实例中保证类型安全,那么您必须在不使用点表示法的情况下编写查询或更新。
要了解有关点表示法的更多信息,请参阅 MongoDB 手册中的点表示法。
相互递归
当两种类型互相包含对方类型的属性时,就称为互递归类型。您可以允许宠物拥有处理程序并定义处理程序来拥有宠物,以便将 Pet 接口更新为互递归。下面的示例引用了互递归的 Pet
和 Handler
接口:
interface Pet { handler?: Handler; name: string; age: number; } interface Handler { pet: Pet; name: string; }
Node.js 驱动程序为通过点符号引用的相互递归类型提供类型安全性,深度可达 8。以下代码段将 string
分配给 number
并引发类型错误,因为引用属性的深度为 4:
database .collection<Pet>("<your collection>") .findOne({'handler.pet.handler.pet.age': "four"});
以上代码片段引发的错误如下:
index.ts(19,59): error TS2769: No overload matches this call. The last overload gave the following error. Type 'string' is not assignable to type 'Condition<number> | undefined'.
当深度大于或等于 8 时,TypeScript 会编译您的代码,但不再对其进行类型检查。以下代码将 string
赋予给 number
属性,但不会导致编译错误,因为引用属性的深度为 10:
database .collection<Pet>("<your collection>") .findOne({'handler.pet.handler.pet.handler.pet.handler.pet.handler.pet.age': "four"});