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

如何通过TypeScript类型收窄避免代码重复?

TypeScript 类型收窄优化:消除重复代码

问题背景

现有Options、Activity、Service三个类以及Item类型,最初编写了两个逻辑高度重复的函数onActivitiesChanged和onServicesChanged。尝试合并为单个函数时,因TypeScript无法自动完成类型收窄而报错;改用switch语句实现后,仍存在明显的代码重复,需要更优的类型收窄方案来避免冗余代码。

相关类型定义

class Options {
  activities: Activity[];
  services: Service[];
}

class Activity {
  activity: string;
  charged: boolean;
  locked: boolean;
}

class Service {
  service: string;
  charged: boolean;
  locked: boolean;
}

type Item = {
  name: string;
  charged: boolean;
  locked: boolean;
};

原重复函数实现

onActivitiesChanged = (items: Item[]) => {
  const newOptions: Options = this.options || new Options();
  newOptions.activities = items.map(item => ({
    activity: item.name,
    charged: item.charged,
    locked: item.locked,
  }));
};

onServicesChanged = (items: Item[]) => {
  const newOptions: Options = this.options || new Options();
  newOptions.services = items.map(item => ({
    service: item.name,
    charged: item.charged,
    locked: item.locked,
  }));
};

合并后仍存在重复的实现

onItemsChanged = ({
  items,
  optionType,
  field,
}:
  | {
      items: Item[];
      optionType: keyof Pick<Options, 'activities'>;
      field: keyof Pick<Activity, 'activity'>;
    }
  | {
      items: Item[];
      optionType: keyof Pick<Options, 'services'>;
      field: keyof Pick<Service, 'service'>;
    }) => {
  const newOptions: Options = this.options || new Options();
  switch (optionType) {
    case 'activities':
      newOptions[optionType] = items.map(item => ({
        [field]: item.name,
        charged: item.charged,
        locked: item.locked,
      }));
      break;
    case 'services':
      newOptions[optionType] = items.map(item => ({
        [field]: item.name,
        charged: item.charged,
        locked: item.locked,
      }));
      break;
  }
};

优化方案:利用常量映射+类型推断

通过定义常量映射关系让TypeScript自动完成类型收窄,同时彻底消除代码重复:

步骤1:定义字段映射常量

用as const让TypeScript保留常量的字面量类型,确保类型收窄的准确性:

const OPTION_TO_FIELD = {
  activities: 'activity' as const,
  services: 'service' as const,
};

// 导出映射的键类型,方便后续复用
type OptionKey = keyof typeof OPTION_TO_FIELD;

步骤2:实现通用函数

利用常量的类型推断,结合类型断言让TypeScript识别目标数组类型:

onItemsChanged = (optionType: OptionKey, items: Item[]) => {
  const newOptions: Options = this.options || new Options();
  const targetField = OPTION_TO_FIELD[optionType];
  
  // 映射Item到目标类型,TypeScript会根据optionType自动推断类型
  newOptions[optionType] = items.map(item => ({
    [targetField]: item.name,
    charged: item.charged,
    locked: item.locked,
  })) as OptionKey extends 'activities' ? Activity[] : Service[];
};

更严谨的泛型版本

如果需要更严格的类型关联,用泛型绑定optionType和对应的目标类型:

// 定义OptionKey与目标类型的关联
type OptionTarget = {
  activities: Activity;
  services: Service;
};

onItemsChanged = <K extends keyof OptionTarget>(optionType: K, items: Item[]) => {
  const newOptions: Options = this.options || new Options();
  const targetField = OPTION_TO_FIELD[optionType];
  
  newOptions[optionType] = items.map(item => ({
    [targetField]: item.name,
    charged: item.charged,
    locked: item.locked,
  })) as OptionTarget[K][];
};

方案优势

  • 彻底消除代码重复:仅需一次map逻辑实现所有类型的转换
  • 类型安全:TypeScript能通过常量映射自动收窄类型,避免类型报错
  • 扩展性强:后续新增类似类型时,只需在OPTION_TO_FIELD和OptionTarget中添加对应项即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 16:35:01