You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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ı

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 19:05:22