如何实现支持实体属性增改的类型安全通用函数?
问题描述
现有一组实体类型,这些类型可引用其他实体(可为空单值或数组形式),示例代码如下:
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
相关产品推荐
相关产品推荐

