实现树结构通用节点接口与递归查找函数解决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
相关产品推荐
相关产品推荐

