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

如何让TypeScript类型包含子类型且自身可作为有效类型

在TypeScript中创建兼具自身类型与嵌套子类型的结构

需求说明

我希望创建一个TypeScript结构(namespace/class/interface/type均可),使其既包含属性与方法的类型声明,又能拥有类似Character.Stats或Character.Currency的嵌套子类型,同时Character本身仍可作为有效类型使用。

我需要让以下classes.ts代码合法:

// 导入Character类型的语句

class Player implements Character {
    constructor (
        public name: string, // 属性
        public stats: Character.Stats, // 子类型标注的属性
        public currency: Character.Currency
    ) {
        this.name = name
        this.stats = stats
        this.currency = currency
    }

    attack(target: Player) { // 参数为自身类的类型
        console.log(`Attacked ${target.name}`)
    }
}

这在当前TypeScript中是否可行?

当前测试代码结构

types/items.ts

export interface Type {
    name: string,
    sprite: string,
}

types/characters.ts

import * as Classes from "../classes";

export interface Stats {
    health: number,
    attack: number,
    defence: number,
}

export interface Currency {
    gold: number,
    ridium: number
}

export interface Type {
    name: string,
    stats: Stats
    currency: Currency

    announce(sentence: string): void,
    attack(target: Type): void,
    heal(item: Classes.HealingItem): void

}

classes.ts

import * as Characters from "./types/characters";
import * as Items from "./types/items"

export class Character implements Characters.Type {

    constructor(
        public name: string,
        public stats: Characters.Stats,
        public currency: Characters.Currency
    ) {
        this.name = name;
        this.stats = stats
        this.currency = currency
    }

    announce(sentence: string) {
        console.log(`I, ${this.name}, ${sentence}`)
    }

    attack(target: Character): void {
        this.announce(`am going to attack ${target.name}`)
    }

    heal(item: HealingItem) {
        this.stats.health += item.healthPoints;
        this.announce(`used ${item.name} to heal my hp from ${this.stats.health - item.healthPoints} to ${this.stats.health}`)
    }

}

export class HealingItem implements Items.Type {

    constructor(
        public name: string,
        public sprite: string,
        public healthPoints: number
    ) {
        this.name = name
        this.sprite = sprite
        this.healthPoints = healthPoints
    }

}

index.ts

import * as Classes from "./classes";

const hero = new Classes.Character("Bob", // name
    { // stats
        health: 100,
        attack: 10,
        defence: 25
    },
    { // currency
        gold: 50,
        ridium: 0
    }
)

const apple = new Classes.HealingItem("Apple", "./sprites/apple.sprite", 25);

hero.heal(apple);
hero.attack(hero);

当前代码可正常运行,但我认为用Items.Type/Characters.Type的命名方式未来可能引发混淆或维护问题。我想要的理想效果如下(注:以下代码不合法,仅用于表达需求):

interface B {
    x: string
}

namespace A {
    export type v1 = number
    export type v2 = boolean
    export type v3 = B
}

let p: A = {
    v1: 9,
    v2: true,
    v3: { x: "some string" }
};
let q: A.v1 = 2;
let r: A.v2 = false;
let s: A.v3 = { x: "strung" };

解决方案:利用TypeScript声明合并

这在TypeScript中完全可行,核心是使用声明合并特性——将同名的接口(作为主类型)与命名空间合并,从而实现:

  • 接口作为Character自身的类型定义
  • 命名空间承载Stats、Currency等嵌套子类型

修改后的types/characters.ts

// 使用类型导入避免循环依赖
export interface Character {
  name: string;
  stats: Character.Stats;
  currency: Character.Currency;
  announce(sentence: string): void;
  attack(target: Character): void;
  heal(item: import("../classes").HealingItem): void;
}

// 同名命名空间,与上方接口合并
export namespace Character {
  export interface Stats {
    health: number;
    attack: number;
    defence: number;
  }

  export interface Currency {
    gold: number;
    ridium: number;
  }
}

修改后的classes.ts

import { Character } from "./types/characters";
import { Type as ItemType } from "./types/items";

export class Player implements Character {
  constructor(
    public name: string,
    public stats: Character.Stats,
    public currency: Character.Currency
  ) {}

  attack(target: Player) {
    console.log(`Attacked ${target.name}`);
  }

  announce(sentence: string): void {
    console.log(`I, ${this.name}, ${sentence}`);
  }

  heal(item: HealingItem): void {
    this.stats.health += item.healthPoints;
    this.announce(`used ${item.name} to heal my hp from ${this.stats.health - item.healthPoints} to ${this.stats.health}`);
  }
}

export class HealingItem implements ItemType {
  constructor(
    public name: string,
    public sprite: string,
    public healthPoints: number
  ) {}
}

关键说明

  1. 声明合并规则:同名的接口和命名空间会被TypeScript自动合并,外部既可以将Character当作类型使用(如implements Character、let char: Character),也可以通过Character.Stats访问嵌套子类型。
  2. 避免循环导入:如果主接口需要引用外部类(如HealingItem),使用import("../classes").HealingItem这种类型导入语法,可避免文件间的循环依赖问题。
  3. 命名空间成员导出:命名空间内的子类型必须用export修饰,才能被外部访问。

验证效果

修改后,你最初目标中的classes.ts代码将完全合法,同时满足:

  • Player类正确实现Character类型
  • 可以用Character.Stats、Character.Currency标注属性类型
  • Character本身可作为有效类型使用

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 08:10:21