如何在TypeScript函数中同时返回类型谓词与实际值?
如何在保留类型谓词的同时让函数返回额外值
问题描述
我有一个带类型谓词的验证函数:
function validate(x: string | null): x is string { return x !== null; }
现在想保留它的类型收窄能力(调用后让TypeScript识别x为string类型),同时让函数返回额外值(比如验证状态描述)。我尝试了下面的写法,但无法实现需求:
function validate(x: string | null): [string, x is string] { const isValid = x !== null; return [isValid ? "valid" : "invalid", isValid]; }
完整业务场景
我的实际业务代码如下:
class Badge { public issuerId: string; public status: string; constructor(issuerId: string, status: string) { this.issuerId = issuerId; this.status = status; } } class ServiceResponse { public code: number; public message: string; constructor(code: number, message: string) { this.code = code; this.message = message; } } function authorizeBadge(badge: Badge | null, userId: string): ServiceResponse | undefined { if (!badge) { return new ServiceResponse(404, "badge not found"); } if (badge.issuerId !== userId) { return new ServiceResponse(403, "unauthorized"); } if (badge.status !== "SUCCESS") { return new ServiceResponse(423, "invalid badge state"); } return; }
调用时遇到问题:即使authorizeBadge返回undefined(表示验证通过),TypeScript依然认为badge是Badge | null类型,无法直接访问Badge的属性:
function updateBadge(badge: Badge | null, userId: string): ServiceResponse { const err = authorizeBadge(badge, userId); if (err) { return err } // 此处badge类型仍为Badge | null,无法直接访问其属性 // 一些业务逻辑... return new ServiceResponse(200, "ok"); }
解决方案
1. 通用验证函数:用泛型条件类型绑定返回值与参数类型
通过泛型条件类型,让函数返回类型与输入参数类型关联,TypeScript会根据返回结果自动收窄参数类型:
function validate<T extends string | null>(x: T): T extends string ? { isValid: true, message: string } : { isValid: false, message: string }; function validate(x: string | null): any { const isValid = x !== null; return { isValid, message: isValid ? "valid" : "invalid" }; } // 使用示例 const testVal = Math.random() > 0.5 ? "test" : null; const result = validate(testVal); if (result.isValid) { // 此处testVal已被收窄为string类型 console.log(testVal.length); } else { // 此处testVal已被收窄为null类型 console.log("值为null"); }
2. 业务场景:重构授权函数的返回类型
把authorizeBadge的返回值改成带状态标记的联合类型,结合泛型条件类型让TypeScript根据返回结果收窄badge类型:
type AuthorizationResult = | { success: true } | { success: false, error: ServiceResponse }; function authorizeBadge<T extends Badge | null>(badge: T, userId: string): T extends Badge ? AuthorizationResult : { success: false, error: ServiceResponse }; function authorizeBadge(badge: Badge | null, userId: string): AuthorizationResult { if (!badge) { return { success: false, error: new ServiceResponse(404, "badge not found") }; } if (badge.issuerId !== userId) { return { success: false, error: new ServiceResponse(403, "unauthorized") }; } if (badge.status !== "SUCCESS") { return { success: false, error: new ServiceResponse(423, "invalid badge state") }; } return { success: true }; } // 使用示例 function updateBadge(badge: Badge | null, userId: string): ServiceResponse { const authResult = authorizeBadge(badge, userId); if (!authResult.success) { return authResult.error; } // 此处badge已被收窄为Badge类型,可直接访问其属性 console.log(badge.issuerId); // 业务逻辑... return new ServiceResponse(200, "ok"); }
3. 简化方案:利用控制流分析
如果不需要复杂泛型,可通过拆分逻辑实现类型收窄:在授权函数返回错误信息后,调用处添加一次类型判断触发TypeScript的控制流分析:
function authorizeBadge(badge: Badge | null, userId: string): ServiceResponse | null { if (!badge) { return new ServiceResponse(404, "badge not found"); } if (badge.issuerId !== userId) { return new ServiceResponse(403, "unauthorized"); } if (badge.status !== "SUCCESS") { return new ServiceResponse(423, "invalid badge state"); } return null; } function updateBadge(badge: Badge | null, userId: string): ServiceResponse { const err = authorizeBadge(badge, userId); if (err) { return err; } // 手动添加类型判断,触发控制流分析 if (!badge) { return new ServiceResponse(500, "unexpected error"); } // 此处badge已被收窄为Badge类型 // 业务逻辑... return new ServiceResponse(200, "ok"); }
该方案实现简单,适合不需要复杂类型关联的场景。
内容的提问来源于stack exchange,提问作者Ahmet Yazıcı
相关产品推荐
相关产品推荐

