实现泛型函数时TypeScript联合类型索引错误的解决咨询
解决TypeScript泛型函数的键不匹配类型错误
问题场景
我需要实现一个通用函数来转换对象变更数据,但当前代码触发了TypeScript类型错误:
Element implicitly has an 'any' type because expression of type "channel" | "id" | "name" can't be used to index type '{ id: string; name: string; source: string; }'. Property 'channel' does not exist on type '{ id: string; name: string; source: string; }'.
核心问题是传入的键集合可能和数据对象的属性不匹配,不知道如何编写类型安全的泛型函数。
原代码如下:
type channelCreateChanges = { change_from : {} change_to : { id : string, name : string, source : string, }, datetime : string, operation : "create", entity : "channel", } type channelUpdateChanges = { change_from : { id : string, name : string, source : string, } change_to : { id : string, name : string, source : string, }, datetime : string, operation : "update", entity : "channel", } type programCreateChanges = { change_from : { } change_to : { channel : string, id : string, name : string, origin : string, }, datetime : string, operation : "create", entity : "program", } type programDeleteChanges = { change_from : { channel : string, id : string, name : string, origin : string, } change_to : { }, datetime : string, operation : "delete", entity : "program", } type dataChangeRow = { from : string, to : string, datetime : string, operation : string, entity : string } const programKeys = [ "id", "channel" ] as const; const channelKeys = [ "id", "name", ] as const; type allChanges = channelCreateChanges | channelUpdateChanges | programCreateChanges | programDeleteChanges type allKeys = typeof programKeys | typeof channelKeys function ANY_CHANGE_LOG_TO_ROWS (data : allChanges, keys : allKeys) : dataChangeRow[] { const rows = [] as dataChangeRow[]; for (const key of keys) { const row = { datetime : data.datetime, entity : data.entity, operation : data.operation } as dataChangeRow; if (data.operation === "create") { row.from = `-`; row.to = `${key}: ${data.change_to[key]}`; } else if (data.operation === "update") { row.from = `${key}: ${data.change_from[key]}`; row.to = `${key}: ${data.change_to[key]}`; } else if (data.operation === "delete") { row.from = `${key}: ${data.change_from[key]}`; row.to = `-`; } rows.push(row); } return rows; }
解决方案
要解决这个问题,需要通过泛型将数据类型和键集合强关联,确保传入的键始终是对应数据对象的有效属性。以下是重构后的代码:
// 定义基础的变更操作类型 type ChangeOperation = "create" | "update" | "delete"; // 用泛型生成实体的变更日志类型 type EntityChangeLog<T, Op extends ChangeOperation> = { datetime: string; operation: Op; entity: string; } & ( Op extends "create" ? { change_from: {}; change_to: T } : Op extends "update" ? { change_from: T; change_to: T } : Op extends "delete" ? { change_from: T; change_to: {} } : never ); // 具体实体的数据结构 type ChannelData = { id: string; name: string; source: string }; type ProgramData = { channel: string; id: string; name: string; origin: string }; // 生成具体的变更日志类型 type ChannelCreateChanges = EntityChangeLog<ChannelData, "create"> & { entity: "channel" }; type ChannelUpdateChanges = EntityChangeLog<ChannelData, "update"> & { entity: "channel" }; type ProgramCreateChanges = EntityChangeLog<ProgramData, "create"> & { entity: "program" }; type ProgramDeleteChanges = EntityChangeLog<ProgramData, "delete"> & { entity: "program" }; type allChanges = ChannelCreateChanges | ChannelUpdateChanges | ProgramCreateChanges | ProgramDeleteChanges; type dataChangeRow = { from: string; to: string; datetime: string; operation: string; entity: string; }; const programKeys = ["id", "channel"] as const; const channelKeys = ["id", "name"] as const; // 泛型函数:关联数据类型T和键集合K function ANY_CHANGE_LOG_TO_ROWS<T, K extends keyof T>( data: EntityChangeLog<T, ChangeOperation> & { entity: string }, keys: readonly K[] ): dataChangeRow[] { const rows: dataChangeRow[] = []; for (const key of keys) { const row: dataChangeRow = { datetime: data.datetime, entity: data.entity, operation: data.operation, from: "-", to: "-" }; if (data.operation === "create") { row.to = `${key}: ${data.change_to[key]}`; } else if (data.operation === "update") { row.from = `${key}: ${data.change_from[key]}`; row.to = `${key}: ${data.change_to[key]}`; } else if (data.operation === "delete") { row.from = `${key}: ${data.change_from[key]}`; } rows.push(row); } return rows; } // 测试调用(类型安全) const channelUpdate: ChannelUpdateChanges = { change_from: { id: "1", name: "old", source: "src" }, change_to: { id: "1", name: "new", source: "src" }, datetime: "2024-01-01", operation: "update", entity: "channel" }; const result = ANY_CHANGE_LOG_TO_ROWS(channelUpdate, channelKeys); console.log(result);
关键改动说明
- 泛型关联类型:使用
T表示实体的数据结构,K extends keyof T确保传入的keys都是T的有效属性,彻底避免键不匹配的类型错误。 - 统一变更类型定义:通过
EntityChangeLog泛型生成不同操作类型的变更日志,减少重复代码,同时让TypeScript能准确识别change_from和change_to的结构。 - 类型收窄优化:利用条件类型自动适配不同操作下的对象结构,函数内的操作分支能被TypeScript正确类型检查。
内容的提问来源于stack exchange,提问作者A. L
相关产品推荐
相关产品推荐

