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

如何实现支持实体属性增改的类型安全通用函数?

问题描述

现有一组实体类型,这些类型可引用其他实体(可为空单值或数组形式),示例代码如下:

type EntityBase = {
  type: string;
}

type EntityA = EntityBase & {
  type: "a",
}

type EntityB = EntityBase & {
  type: "b",
}

type EntityC = EntityBase & {
  type: "c",
}

type EntityTest = EntityBase & {
  type: "test",
  genericEntityList: Entity[],
  singleEntityAorNull: EntityA | null,
  entityBList: EntityB[],
  singleEntityCorNull: EntityC | null
}

type Entity = EntityA | EntityB | EntityC | EntityTest;

需要创建一个通用函数,满足以下要求:

  • 接收某实体的实例
  • 接收需添加(数组类型)或设置(单值类型)的属性名称
  • 接收要添加/设置到该字段的实体

函数签名大致如下:

function addEntity<ParentEntityClass extends Entity,
                   ParentEntityPropertyKey extends keyof ParentEntityClass,
                   EntityClass extends Entity>
                  (parentEntity: EntityBase,
                   propertyKey: ParentEntityPropertyKey,
                   entity: EntityClass) {
    ...
}

要求该函数具备类型安全性:无法传入非实体相关的无效属性键,也无法传入与目标属性不兼容的实体,请问该如何实现?

实现方案

要实现类型安全的addEntity函数,核心是通过精确的类型约束,让TypeScript自动校验属性键的合法性以及实体与目标字段的兼容性。具体实现如下:

1. 定义辅助工具类型

先通过工具类型区分数组/单值属性的实体类型,并筛选出合法的属性键:

// 提取数组类型中的元素实体类型
type ElementType<T> = T extends (infer U)[] ? U : never;

// 提取单值(允许null)属性中的实体类型
type TargetEntityType<T> = T extends (infer U) | null ? U : never;

// 筛选出实体中所有用于引用其他实体的属性键
type ValidEntityPropertyKeys<T extends Entity> = {
  [K in keyof T]: T[K] extends Entity[] | (Entity | null) ? K : never;
}[keyof T];

2. 实现类型安全的函数

通过泛型参数绑定实体、属性键和目标实体类型,确保类型完全匹配:

function addEntity<
  ParentEntity extends Entity,
  PropKey extends ValidEntityPropertyKeys<ParentEntity>,
  // 根据属性类型自动推导允许传入的实体类型
  TargetEntity extends (ParentEntity[PropKey] extends Entity[] 
    ? ElementType<ParentEntity[PropKey]> 
    : TargetEntityType<ParentEntity[PropKey]>)
>(
  parentEntity: ParentEntity,
  propertyKey: PropKey,
  entity: TargetEntity
): void {
  // 处理数组类型:追加元素
  if (Array.isArray(parentEntity[propertyKey])) {
    (parentEntity[propertyKey] as Entity[]).push(entity);
  } 
  // 处理单值类型:覆盖原有值(包括null)
  else {
    (parentEntity[propertyKey] as Entity | null) = entity;
  }
}

3. 类型安全验证示例

现在这个函数会自动拦截非法操作:

const testEntity: EntityTest = {
  type: "test",
  genericEntityList: [],
  singleEntityAorNull: null,
  entityBList: [],
  singleEntityCorNull: null
};

// ✅ 合法:给genericEntityList添加任意Entity类型
addEntity(testEntity, "genericEntityList", { type: "a" });

// ✅ 合法:给singleEntityAorNull设置EntityA
addEntity(testEntity, "singleEntityAorNull", { type: "a" });

// ✅ 合法:给entityBList添加EntityB
addEntity(testEntity, "entityBList", { type: "b" });

// ❌ 非法:给entityBList传入EntityA,类型不兼容
addEntity(testEntity, "entityBList", { type: "a" });

// ❌ 非法:传入非实体引用属性type
addEntity(testEntity, "type", { type: "a" });

// ❌ 非法:给singleEntityCorNull传入EntityB,类型不兼容
addEntity(testEntity, "singleEntityCorNull", { type: "b" });

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 04:31:01