如何在TypeScript中为Mongoose关联填充字段定义类型?
如何为Mongoose中可填充字段定义TypeScript类型?
我有一个Mongoose字段,它既可以是ObjectId,也可以是填充后的完整文档,尝试用TypeScript定义类型时遇到了问题,相关代码如下:
import { InferSchemaType, Schema, Types, model } from 'mongoose'; const personSchema = new Schema({ _id: Schema.Types.ObjectId, name: String, age: Number, }); const storySchema = new Schema({ author: { type: Schema.Types.ObjectId, ref: 'Person' }, title: String, }); export type PersonDao = { _id: Types.ObjectId } & InferSchemaType<typeof personSchema>; export type StoryDao = { _id: Types.ObjectId; author: Types.ObjectId | PersonDao } & Omit<InferSchemaType<typeof storySchema>, 'author'>; export const Person = model('Person', personSchema); export const Story = model('Story', storySchema); Story.findOne({ _id: 'fakeId' }) .populate<StoryDao>('author') .exec() .then((story: StoryDao) => { console.log(story.author.name); // 报错 });
此时访问story.author.name会触发TypeScript错误:
Property 'name' does not exist on type 'ObjectId | PersonDao'. Property 'name' does not exist on type 'ObjectId'.ts(2339)
如果把类型定义改成仅允许PersonDao:
export type StoryDao = { _id: Types.ObjectId; author: PersonDao } & Omit<InferSchemaType<typeof storySchema>, 'author'>; export const story: StoryDao = { _id: new Types.ObjectId(), author: new Types.ObjectId(), // 报错 title: 'fakeTitle', };
name的访问错误消失了,但创建对象时author字段又报错:
type 'ObjectId' is not assignable to type 'PersonDao'. Type 'ObjectId' is missing the following properties from type '{ createdAt: NativeDate; updatedAt: NativeDate; }': createdAt, updatedAtts(2322)
这本质是TypeScript联合类型的限制,比如下面的示例同样会报错:
type Test = number | { name: string } const test: Test = {} as Test; console.log('DEBUG: ', test.name); // 报错
解决方案
方案1:区分未填充/已填充的类型
分别定义两种类型,对应字段未填充和已填充的状态,让TypeScript能精准推断:
import { InferSchemaType, Schema, Types, model } from 'mongoose'; const personSchema = new Schema({ _id: Schema.Types.ObjectId, name: String, age: Number, }); const storySchema = new Schema({ author: { type: Schema.Types.ObjectId, ref: 'Person' }, title: String, }); // 基础Person类型 export type PersonDao = { _id: Types.ObjectId } & InferSchemaType<typeof personSchema>; // 未填充author的Story类型 export type StoryDao = { _id: Types.ObjectId } & InferSchemaType<typeof storySchema>; // 已填充author的Story类型 export type StoryWithAuthorDao = Omit<StoryDao, 'author'> & { author: PersonDao }; export const Person = model('Person', personSchema); export const Story = model('Story', storySchema); // 查询并填充后,使用已填充类型 Story.findOne({ _id: 'fakeId' }) .populate<StoryWithAuthorDao>('author') .exec() .then((story: StoryWithAuthorDao) => { console.log(story.author.name); // 正常无报错 }); // 创建未填充的Story对象,使用基础类型 const story: StoryDao = { _id: new Types.ObjectId(), author: new Types.ObjectId(), // 正常无报错 title: 'fakeTitle', };
方案2:使用类型守卫判断字段类型
定义类型守卫函数,在运行时判断字段是否为填充后的文档,让TypeScript根据判断结果自动推断类型:
import { InferSchemaType, Schema, Types, model } from 'mongoose'; const personSchema = new Schema({ _id: Schema.Types.ObjectId, name: String, age: Number, }); const storySchema = new Schema({ author: { type: Schema.Types.ObjectId, ref: 'Person' }, title: String, }); export type PersonDao = { _id: Types.ObjectId } & InferSchemaType<typeof personSchema>; export type StoryDao = { _id: Types.ObjectId; author: Types.ObjectId | PersonDao } & Omit<InferSchemaType<typeof storySchema>, 'author'>; // 类型守卫函数:判断传入对象是否为PersonDao类型 function isPersonDao(obj: Types.ObjectId | PersonDao): obj is PersonDao { return typeof (obj as PersonDao).name !== 'undefined'; } export const Person = model('Person', personSchema); export const Story = model('Story', storySchema); Story.findOne({ _id: 'fakeId' }) .populate<StoryDao>('author') .exec() .then((story: StoryDao) => { if (isPersonDao(story.author)) { console.log(story.author.name); // 类型推断正确,无报错 } else { // 处理author为ObjectId的逻辑 console.log('Author is an ObjectId:', story.author); } });
内容的提问来源于stack exchange,提问作者TheSmartMonkey
相关产品推荐
相关产品推荐

