如何通过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
相关产品推荐
相关产品推荐

