如何定义TypeScript泛型类型AllPropertiesAccessible递归移除后代Inaccessible类型
问题描述
我有一批TypeScript类型,使用共享的Inaccessible接口替代用户无权限访问的对象。但我常处理更复杂的嵌套数据(比如GraphQL接口返回的数据),且编写的组件仅在整个对象可访问时才能正常工作。这种场景很常见,因此我希望能编写一个类型守卫,借助泛型类型AllPropertiesAccessible<T>递归移除基础类型所有后代中的Inaccessible类型。
需求示例如下,核心是AllPropertiesAccessible<T>需为泛型,以适配包含Inaccessible后代的其他类型:
interface Inaccessible { invalidId: string; } interface Person { id: string; name: string; } interface Node { children: Array<Node | Inaccessible>; user: Person | Inaccessible; spouse: Person | Inaccessible; } interface FamilyTree { rootNode: Node | Inaccessible; } function isFamilyTreeAccessible(familyTree: FamilyTree): familyTree is AllPropertiesAccessible<FamilyTree> { // 递归验证树中所有元素是否可访问的逻辑 } function FamilyTreeOrInaccessibleDisplay({familyTree}: {familyTree: FamilyTree}): JSX.Element { if (isFamilyTreeAccessible(familyTree)) { return <FamilyTreeDisplay familyTree={familyTree} /> } else { return <NotAccessibleMessage /> } } function FamilyTreeDisplay({familyTree}: {familyTree: AllPropertiesAccessible<FamilyTree>}) {} function NotAccessibleMessage() { return <div>Inaccessible</div> }
在该示例中,FamilyTree类型的多个后代可能为Inaccessible,AllPropertiesAccessible<FamilyTree>等价于:
interface AccessibleNode { children: Array<AccessibleNode>; user: Person; spouse: Person; } interface AccessibleFamilyTree { rootNode: AccessibleNode; }
请问能否在TypeScript中编写这样的AllPropertiesAccessible类型?
解决方案
可以实现这个泛型类型,核心思路是递归遍历目标类型的所有层级,移除所有Inaccessible类型,并对剩余类型做同样的递归处理。
类型定义
type AllPropertiesAccessible<T> = // 先从联合类型中排除所有Inaccessible分支 Exclude<T, Inaccessible> extends infer U ? U extends object ? U extends Array<infer ItemType> // 处理数组:递归处理数组元素类型 ? Array<AllPropertiesAccessible<ItemType>> // 处理对象:遍历每个属性,递归应用类型转换 : { [Key in keyof U]: AllPropertiesAccessible<U[Key]> } // 基础类型(如string、number等)直接保留 : U : never;
代码解释
- 排除Inaccessible:通过
Exclude<T, Inaccessible>把所有联合类型中的Inaccessible分支移除,确保只保留可访问的类型。 - 递归处理对象:如果剩余类型是对象(非数组),遍历其所有属性,对每个属性值递归应用
AllPropertiesAccessible,实现深层嵌套处理。 - 递归处理数组:如果剩余类型是数组,对数组元素类型递归应用
AllPropertiesAccessible,确保数组内的嵌套结构也被处理。 - 保留基础类型:对于字符串、数字等基础类型,直接返回原类型,无需处理。
完整示例(含类型守卫实现)
interface Inaccessible { invalidId: string; } interface Person { id: string; name: string; } interface Node { children: Array<Node | Inaccessible>; user: Person | Inaccessible; spouse: Person | Inaccessible; } interface FamilyTree { rootNode: Node | Inaccessible; } // 核心泛型类型 type AllPropertiesAccessible<T> = Exclude<T, Inaccessible> extends infer U ? U extends object ? U extends Array<infer ItemType> ? Array<AllPropertiesAccessible<ItemType>> : { [Key in keyof U]: AllPropertiesAccessible<U[Key]> } : U : never; // 类型守卫实现:递归检查所有层级是否不含Inaccessible function isFamilyTreeAccessible(familyTree: FamilyTree): familyTree is AllPropertiesAccessible<FamilyTree> { // 检查单个对象是否是Inaccessible const isInaccessible = (obj: unknown): obj is Inaccessible => { return typeof obj === 'object' && obj !== null && 'invalidId' in obj; }; // 递归检查Node类型 const checkNode = (node: Node | Inaccessible): node is AllPropertiesAccessible<Node> => { if (isInaccessible(node)) return false; // 检查user和spouse if (isInaccessible(node.user) || isInaccessible(node.spouse)) return false; // 递归检查所有children return node.children.every(child => { if (isInaccessible(child)) return false; return checkNode(child); }); }; // 检查根节点 if (isInaccessible(familyTree.rootNode)) return false; return checkNode(familyTree.rootNode); } // 组件示例 function FamilyTreeOrInaccessibleDisplay({familyTree}: {familyTree: FamilyTree}): JSX.Element { if (isFamilyTreeAccessible(familyTree)) { return <FamilyTreeDisplay familyTree={familyTree} />; } else { return <NotAccessibleMessage />; } } function FamilyTreeDisplay({familyTree}: {familyTree: AllPropertiesAccessible<FamilyTree>}) { // 此处可以安全访问所有属性,无需处理Inaccessible分支 return ( <div> <h1>Family Tree</h1> <p>Root User: {familyTree.rootNode.user.name}</p> {/* 递归渲染children等 */} </div> ); } function NotAccessibleMessage() { return <div>Inaccessible</div>; }
验证效果
在上述代码中,当isFamilyTreeAccessible返回true时,TypeScript会自动推断familyTree的类型为AllPropertiesAccessible<FamilyTree>,此时在FamilyTreeDisplay组件中,所有属性(如familyTree.rootNode.user.name)都可以安全访问,不会出现Inaccessible类型的报错。
内容的提问来源于stack exchange,提问作者hijodelsol14
相关产品推荐
相关产品推荐

