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

如何定义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;

代码解释

  1. 排除Inaccessible:通过Exclude<T, Inaccessible>把所有联合类型中的Inaccessible分支移除,确保只保留可访问的类型。
  2. 递归处理对象:如果剩余类型是对象(非数组),遍历其所有属性,对每个属性值递归应用AllPropertiesAccessible,实现深层嵌套处理。
  3. 递归处理数组:如果剩余类型是数组,对数组元素类型递归应用AllPropertiesAccessible,确保数组内的嵌套结构也被处理。
  4. 保留基础类型:对于字符串、数字等基础类型,直接返回原类型,无需处理。

完整示例(含类型守卫实现)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 07:25:24