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

如何在TypeScript的validateField函数中正确收窄类型?

如何在validateField函数中正确收窄TypeScript类型?

先看问题代码及报错场景:

type TStringValidator = (v: string) => void;
type TNumberValidator = (v: number) => void;

type TFields = 'inn' | 'amount';

interface IValidators {
  inn: TStringValidator[];
  amount: TNumberValidator[];
}

const handleString: TStringValidator = (value: string) => {};
const handleNumber: TNumberValidator = (value: number) => {};

const validators = {
  inn: [handleString],
  amount: [handleNumber],
} satisfies IValidators;

function validateField(field: TFields, value: string | number) {
  const fieldValidators = validators[field];

  for (const validator of fieldValidators) {
    // 触发TS错误:Argument of type 'string | number' is not assignable to parameter of type 'never'
    const result = validator(value);
  }

  return;
}

问题原因

TypeScript无法自动建立field和value的类型绑定:field是'inn' | 'amount'联合类型,value是string | number联合类型,两者的对应关系未被编译器识别。此时validators[field]的类型为TStringValidator[] | TNumberValidator[],调用validator时,参数需要同时满足两种校验器的参数类型(即string & number,等价于never),因此报错。

解决方案1:泛型绑定类型关联

通过泛型约束,让field和value的类型一一对应,编译器就能正确推断校验器的参数类型:

type TStringValidator = (v: string) => void;
type TNumberValidator = (v: number) => void;

type TFields = 'inn' | 'amount';

interface IValidators {
  inn: TStringValidator[];
  amount: TNumberValidator[];
}

const handleString: TStringValidator = (value: string) => {};
const handleNumber: TNumberValidator = (value: number) => {};

const validators = {
  inn: [handleString],
  amount: [handleNumber],
} satisfies IValidators;

// 用泛型F约束field,关联对应字段的value类型
function validateField<F extends TFields>(
  field: F,
  value: Parameters<IValidators[F][number]>[0]
) {
  const fieldValidators = validators[field];

  for (const validator of fieldValidators) {
    validator(value); // 类型匹配,无报错
  }

  return;
}

// 类型检查生效
validateField('inn', '123456'); // 合法
validateField('amount', 100); // 合法
validateField('inn', 123); // 报错:'inn'对应的value必须是string
validateField('amount', '100'); // 报错:'amount'对应的value必须是number

解决方案2:函数重载

如果偏好显式定义每种字段的参数类型,可以用函数重载实现:

type TStringValidator = (v: string) => void;
type TNumberValidator = (v: number) => void;

type TFields = 'inn' | 'amount';

interface IValidators {
  inn: TStringValidator[];
  amount: TNumberValidator[];
}

const handleString: TStringValidator = (value: string) => {};
const handleNumber: TNumberValidator = (value: number) => {};

const validators = {
  inn: [handleString],
  amount: [handleNumber],
} satisfies IValidators;

// 定义重载签名,明确字段与参数类型的对应关系
function validateField(field: 'inn', value: string): void;
function validateField(field: 'amount', value: number): void;
// 实现签名
function validateField(field: TFields, value: string | number) {
  const fieldValidators = validators[field];

  for (const validator of fieldValidators) {
    // 通过field判断自动收窄类型
    if (field === 'inn') {
      validator(value);
    } else {
      validator(value);
    }
  }

  return;
}

这种方式下,编译器会根据重载签名做参数类型校验,实现内部也能通过field的判断自动收窄类型,无需额外断言。

内容的提问来源于stack exchange,提问作者Pavel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 18:53:14