TypeScript条件返回指定类型仍报类型不兼容错误,求解决方案
问题背景
将User类的createdEvents字段类型定义为createdEvents[] | string后,触发如下TypeScript错误:
Type '(parent: User, args: InputShapeFromFields<InputFieldMap<"InputObject" | "Arg">>, context: { currentUser: User; }) => "No events created" | (string | createdEvents[])[]' is not assignable to type 'Resolver<User, InputShapeFromFields<InputFieldMap<"InputObject" | "Arg">>, { currentUser: User; }, createdEvents[] | null | undefined, "No events created" | (string | createdEvents[])[]>'.
Type '"No events created" | (string | createdEvents[])[]' is not assignable to type 'MaybePromise<readonly MaybePromise[]> | null | undefined'.
Type '"No events created"' is not assignable to type 'MaybePromise<readonly MaybePromise[]> | null | undefined'.
尝试过直接返回parent.createdEvents,或在值为undefined时返回null/undefined/空数组,均未解决问题;仅在类型定义后添加| any可暂时屏蔽错误。
相关代码如下:
User类定义
export class User { _id: ObjectId; name: string; email: string; password?: string; availableWeights?: number[]; createdEvents?: createdEvents[] | string; signedUpEvents?: userSignUp[]; constructor(id: ObjectId, name: string, email: string, password?: string, availableWeights?: number[], createdEvents?: createdEvents[] | string, signedUpEvents?: userSignUp[]) { this._id = id; this.name = name; this.email = email; this.password = password; this.availableWeights = availableWeights; this.createdEvents = createdEvents; this.signedUpEvents = signedUpEvents; } }
报错的Resolver代码
createdEvents: t.field({ type: [createdEvents], resolve: (parent, args, context) => { if (parent.createdEvents == undefined || parent.createdEvents == null || parent.createdEvents.length == 0) { return "No events created"; } else { return parent.createdEvents; } } }),
createdEvents类定义
export class createdEvents { _id: ObjectId; eventName: string; eventDate: Date; eventDescription: string; cost?: string; eventLink?: string; weights?: weightsForUserCreatedEvents[]; constructor(_id: ObjectId, eventName: string, eventDate: Date, eventDescription: string, cost?: string, eventLink?: string, weights?: weightsForUserCreatedEvents[]) { this._id = _id; this.eventName = eventName; this.eventDate = eventDate; this.eventDescription = eventDescription; this.cost = cost; this.eventLink = eventLink; this.weights = weights; } }
根本原因
- Resolver返回类型与字段声明不匹配:Resolver中
type: [createdEvents]明确指定返回类型是createdEvents[](或兼容的null/undefined),但代码中返回了字符串"No events created",完全不符合字段声明的类型要求。 - 数据模型类型设计错误:
createdEvents字段定义为createdEvents[] | string,属于数据类型与提示信息的混淆——数据字段应存储业务数据,提示信息属于视图层或业务逻辑的输出处理,不应混入数据模型的类型定义。 - 逻辑判断漏洞:当
parent.createdEvents是字符串时,parent.createdEvents.length读取的是字符串长度而非数组长度,导致逻辑判断混乱,进一步引发类型冲突。
正确解决方案
方案1:规范数据模型与Resolver类型(推荐)
第一步:修正User类字段类型
将createdEvents字段类型统一为数组类型,移除string选项,确保数据模型仅存储业务数据:
export class User { // ... 其他字段保持不变 createdEvents?: createdEvents[]; // 移除| string // ... 构造函数对应参数同步修改 constructor(id: ObjectId, name: string, email: string, password?: string, availableWeights?: number[], createdEvents?: createdEvents[], signedUpEvents?: userSignUp[]) { // ... 其他赋值逻辑不变 this.createdEvents = createdEvents; } }
第二步:修正Resolver逻辑
确保返回类型与字段声明一致,用空数组或null表示无事件,提示信息交由上层业务或视图处理:
createdEvents: t.field({ type: [createdEvents], resolve: (parent, args, context) => { // 处理undefined/null/空数组的情况,返回空数组 return parent.createdEvents ?? []; // 若允许返回null,可改为:return parent.createdEvents ?? null; } }),
方案2:特殊业务场景下的兼容处理(不推荐)
若业务硬要求Resolver返回提示字符串,需修改字段的类型声明为联合类型,但这种设计不符合GraphQL类型规范,需谨慎使用:
// 修改Resolver的type定义,允许数组或字符串类型 createdEvents: t.field({ type: t.union([t.list(t.type(createdEvents)), t.string()]), resolve: (parent, args, context) => { // 先判断类型,避免length判断错误 if (!parent.createdEvents || (Array.isArray(parent.createdEvents) && parent.createdEvents.length === 0)) { return "No events created"; } // 确保返回合法类型 return Array.isArray(parent.createdEvents) ? parent.createdEvents : []; } }),
同时保留User类中createdEvents?: createdEvents[] | string的类型定义,但需严格控制赋值逻辑,避免类型混乱。
总结
核心问题是数据模型类型与Resolver返回类型不匹配,以及业务逻辑中数据与提示信息的混淆。推荐使用方案1,严格区分数据存储与业务提示,遵循类型规范,避免类型污染。
内容的提问来源于stack exchange,提问作者Wayne

