You need to enable JavaScript to run this app. 实现树结构通用节点接口与递归查找函数解决TS2345报错
优惠活动
大模型
产品
解决方案
定价
更多

实现树结构通用节点接口与递归查找函数解决TS2345报错

报错根因

问题出在泛型的类型层级不匹配上:

  • 基础接口TreeNode声明的children字段,类型是固定的父类TreeNode[]
  • 但泛型函数findInTrees要求入参必须是TreeType[],也就是继承自TreeNode的具体子类型数组
  • TS的类型检查规则是:父类型数组不能隐式赋值给任意子类型数组,就算你实际业务里children存的都是和根节点同类型的对象,只要接口层面没有声明这个对应关系,就会触发类型不匹配的错误。
可选解决方案

方案1:最小改动——递归调用位置加类型断言

适合不想大改现有接口定义的场景,毕竟你定义的TableOfContents本身就要求每层子节点的类型和根节点一致,这个断言是完全安全的,没有类型安全损失:

export const findInTrees = <TreeType extends TreeNode>(
  trees: TreeType[],
  callback: (el: TreeType) => boolean
): TreeType | null => {
  const matchNode = trees.find(tree => callback(tree));
  if (matchNode) {
    return matchNode;
  }
  for (const tree of trees) {
    if (tree.children) {
      // 断言children为同类型节点数组,适配递归入参要求
      const result = findInTrees(tree.children as TreeType[], callback);
      if (result) {
        return result;
      }
    }
  }
  return null;
};

方案2:结构类型约束——从规则层面消除类型不匹配

不用依赖之前定义的TreeNode基础接口,直接用TS的结构类型特性给泛型做约束,明确函数处理的树结构,每层节点都是同类型的:

// 泛型约束从固定TreeNode,改成要求节点带同类型的可选children数组
export const findInTrees = <TreeType extends { children?: TreeType[] }>(
  trees: TreeType[],
  callback: (el: TreeType) => boolean
): TreeType | null => {
  const matchNode = trees.find(tree => callback(tree));
  if (matchNode) return matchNode;
  for (const tree of trees) {
    if (tree.children) {
      // 此时children的类型本身就是TreeType[],完全匹配入参,不需要额外断言
      const result = findInTrees(tree.children, callback);
      if (result) return result;
    }
  }
  return null;
};

这个方案的好处是完全不改动你现有的TableOfContents接口定义,因为它本身就符合这个结构约束,而且从根上让TS理解你写这个递归函数的场景逻辑——同个树结构的所有层级,节点类型完全统一。

方案3:调整基础接口的自关联泛型

如果后续还有其他树结构的搜索需求,可以把基础的TreeNode接口改成带自关联泛型的结构,让所有继承它的子接口,都自动带上同类型的children声明:

export interface TreeNode<NodeType = any> {
  [key: string]: any;
  children?: NodeType[];
}

// 业务接口正常继承,自动把children类型绑定为自身类型
export interface TableOfContents extends TreeNode<TableOfContents> {
  id: number;
  title: string;
  isExpanded: boolean;
}

export const findInTrees = <TreeType extends TreeNode>(
  trees: TreeType[],
  callback: (el: TreeType) => boolean
): TreeType | null => {
  const matchNode = trees.find(tree => callback(tree));
  if (matchNode) return matchNode;
  for (const tree of trees) {
    if (tree.children) {
      const result = findInTrees(tree.children as TreeType[], callback);
      if (result) return result;
    }
  }
  return null;
};
不推荐的改法
  • 不要为了消错把callback的参数类型改成TreeNode,会丢失泛型带来的类型提示,调用时没法拿到TableOfContents上id、title这些自定义字段的类型推导
  • 不要把函数返回值类型改成TreeNode | null,会让所有调用方拿到结果后还要手动做类型断言,完全失去了泛型函数的设计价值

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.11 16:45:03