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

TypeScript中递归定义Symbol父类型链至BookClass的实现问询

解决TypeScript递归父类型链的类型定义问题

我有一套自定义的层级树形结构,所有节点都继承自BaseSymbol基类,每个子类都有parent属性指向父节点。需要实现getParentList函数,传入任意子类实例后,返回从该节点到根节点BookClass的所有父节点组成的数组,并且返回值的类型要精确涵盖所有可能的父节点类型。

尝试用递归类型表达这个类型链时,遇到了像Section这种parent包含自身的递归场景,会导致类型无限循环,无法正常定义。

原代码示例

// 自定义基类,避免和内置Symbol冲突
class BaseSymbol {}
class BookClass extends BaseSymbol {}

class Sentence extends BaseSymbol {
    parent: Paragraph
}

class Paragraph extends BaseSymbol {
    parent: Section
}

class Section extends BaseSymbol {
    parent: BookClass | Section | Chapter
}

class Chapter extends BaseSymbol {
    parent: BookClass
}

// 期望:传入Paragraph实例时,返回类型为(Paragraph | Section | Chapter | BookClass)[]
function getParentList<T extends BaseSymbol>(object: T): Parentize<T>[] {
    const list: Parentize<T>[] = []
    list.push(object)
    let current: T | BaseSymbol = object
    let parent = current.parent
    while (parent && !(parent instanceof BookClass)) {
        list.push(parent)
        current = parent as T
        parent = current.parent
    }
    if (parent) list.push(parent)
    return list
}

解决方案:带终止条件的递归条件类型

要处理递归父类型的问题,我们可以用带终止条件的递归条件类型,结合累积类型参数来收集所有可能的父节点类型,同时避免无限循环。核心思路是:

  • 每次递归时,把当前节点的parent类型加入累积集合
  • 当遇到BookClass或者当前类型已经在累积集合中时,终止递归(避免循环)

类型定义实现

// 辅助类型:递归收集父类型链,Acc用于累积已收集的类型
type ParentizeChain<T extends BaseSymbol, Acc extends BaseSymbol = T> = 
    // 终止条件1:当前节点的父节点是根节点BookClass,返回所有累积类型+BookClass
    T['parent'] extends BookClass 
        ? Acc | BookClass
        // 终止条件2:当前节点的父节点已经在累积集合中(循环),返回累积类型
        : T['parent'] extends Acc
            ? Acc
            // 递归:将父节点加入累积集合,继续处理父节点的父节点
            : ParentizeChain<T['parent'], Acc | T['parent']>

// 最终对外暴露的类型,即所有可能的节点类型联合
type Parentize<T extends BaseSymbol> = ParentizeChain<T>

修正后的getParentList函数

function getParentList<T extends BaseSymbol>(object: T): Parentize<T>[] {
    const list: Parentize<T>[] = []
    let current: T | BaseSymbol = object
    list.push(current)
    
    while (true) {
        const parent = current.parent
        if (!parent) break
        list.push(parent)
        if (parent instanceof BookClass) break
        // 类型断言:确保父节点属于已收集的类型范围
        current = parent as Parentize<T>
    }
    
    return list
}

效果验证

// 测试1:传入Paragraph实例
const para = new Paragraph()
const paraParents = getParentList(para)
// paraParents的类型自动推导为:(Paragraph | Section | Chapter | BookClass)[]

// 测试2:传入Section实例
const section = new Section()
const sectionParents = getParentList(section)
// sectionParents的类型自动推导为:(Section | Chapter | BookClass)[]

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 22:25:17