关于为任意数量泛型类型编写类型守卫的可行性及Payload CMS多态关联字段类型的技术咨询
关于为任意数量泛型类型编写类型守卫的可行性及Payload CMS多态关联字段类型的技术咨询
嘿,我来聊聊你提到的Payload CMS里多态关联字段的TypeScript类型问题,还有怎么用泛型批量编写类型守卫的可行性~
首先先把你给出的Payload里的多态关联类型片段整理完整(看起来最后有个笔误,应该是TypeC对吧?):
在Payload CMS中,多态关联的数组字段通常会有如下的TypeScript类型结构:
interface TypeA { a: 1 } interface TypeB { b: 2 } interface TypeC { c: 3 } type Example = { items: | { relationTo: 'type-a'; value: number | TypeA; } | { relationTo: 'type-b'; value: number | TypeB; } | { relationTo: 'type-c'; value: number | TypeC; }[]; }
这种类型的特点很明确:每个数组项通过relationTo的字符串值标记它关联的实体类型,value字段要么是实体的ID(数字类型),要么是实体本身。如果关联类型少,手动写每个分支的类型守卫还好,但类型多了就会非常繁琐——这应该就是你想找泛型方案的原因对吧?
答案是完全可以用泛型来批量生成类型守卫和对应的关联类型,下面给你具体的实现思路:
第一步:定义基础泛型关联项类型
先抽取出通用的多态关联项结构,用泛型来复用:
// 单个多态关联项的泛型类型 type PolymorphicItem<TypeKey extends string, EntityType> = { relationTo: TypeKey; value: number | EntityType; };
第二步:生成批量的联合类型
如果有N个关联类型,我们可以用元组来传递类型标识和实体接口,自动生成对应的联合类型:
// 从类型元组生成多态关联数组的泛型类型 type PolymorphicItems<TypeMap extends readonly [string, object][]> = { items: TypeMap extends readonly [infer Key, infer Entity][] ? Key extends string ? Entity extends object ? PolymorphicItem<Key, Entity> : never : never : never; }[];
比如用它来生成你的Example类型:
// 用元组定义所有关联类型的映射 type AllRelations = [ ['type-a', TypeA], ['type-b', TypeB], ['type-c', TypeC] ]; // 自动生成和之前Example一样的类型 type Example = PolymorphicItems<AllRelations>;
第三步:泛型生成类型守卫
接下来写一个泛型函数,传入类型标识字符串,就能自动生成对应的类型守卫:
// 泛型类型守卫生成器 function createPolymorphicGuard<TypeKey extends string, EntityType>(typeKey: TypeKey) { return (item: unknown): item is PolymorphicItem<TypeKey, EntityType> => { // 先判断item是对象,然后检查relationTo是否匹配 return ( typeof item === 'object' && item !== null && 'relationTo' in item && (item as { relationTo?: unknown }).relationTo === typeKey ); }; }
用这个生成器创建各个类型的守卫:
const isTypeAItem = createPolymorphicGuard<'type-a', TypeA>('type-a'); const isTypeBItem = createPolymorphicGuard<'type-b', TypeB>('type-b'); const isTypeCItem = createPolymorphicGuard<'type-c', TypeC>('type-c');
第四步:使用类型守卫
在业务代码里,就能通过这些守卫自动推断类型:
function processAssociatedItems(items: Example['items']) { items.forEach(item => { if (isTypeAItem(item)) { // 这里item.value会被推断为 number | TypeA if ('a' in item.value) { console.log('TypeA实体的a值:', item.value.a); } else { console.log('TypeA的ID:', item.value); } } else if (isTypeBItem(item)) { if ('b' in item.value) { console.log('TypeB实体的b值:', item.value.b); } } else if (isTypeCItem(item)) { if ('c' in item.value) { console.log('TypeC实体的c值:', item.value.c); } } }); }
这种方案的好处是,后续新增关联类型时,只需要在AllRelations元组里加一行,再用生成器创建对应的守卫就行,不用重复写相似的类型判断逻辑,非常适合Payload CMS这种可能有大量多态关联的场景。
备注:内容来源于stack exchange,提问作者Violet Rose
相关产品推荐
相关产品推荐

