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

实现泛型函数时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);

关键改动说明

  1. 泛型关联类型:使用T表示实体的数据结构,K extends keyof T确保传入的keys都是T的有效属性,彻底避免键不匹配的类型错误。
  2. 统一变更类型定义:通过EntityChangeLog泛型生成不同操作类型的变更日志,减少重复代码,同时让TypeScript能准确识别change_from和change_to的结构。
  3. 类型收窄优化:利用条件类型自动适配不同操作下的对象结构,函数内的操作分支能被TypeScript正确类型检查。

内容的提问来源于stack exchange,提问作者A. L

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 08:15:36