TypeScript 中的模块系统
1. 模块基础概念
模块是一种组织和复用代码的方式,可以把相关的代码组织在一起,并控制其访问范围。
// 文件: src/systems/HeroSystem.ts
export class HeroSystem {
createHero() { /* ... */ }
levelUp() { /* ... */ }
}
// 文件: src/App.ts
import { HeroSystem } from "./systems/HeroSystem";
let heroSystem = new HeroSystem();
2. 导出(Export)
有几种不同的导出方式:
// 1. 默认导出 (src/systems/ItemSystem.ts)
export default class ItemSystem {
createItem() { /* ... */ }
removeItem() { /* ... */ }
}
// 2. 命名导出 (src/config/GameConfig.ts)
export const HERO_STATS = {
baseHealth: 500,
baseMana: 300
};
export function calculateDamage() {
// ...
}
// 3. 多个导出 (src/utils/GameUtils.ts)
export class MathUtils {
static random(min: number, max: number) { /* ... */ }
}
export class StringUtils {
static format(text: string, ...args: any[]) { /* ... */ }
}
3. 导入(Import)
对应的导入方式:
// 1. 导入默认导出
import ItemSystem from "./systems/ItemSystem";
// 2. 导入命名导出
import { HERO_STATS, calculateDamage } from "./config/GameConfig";
// 3. 导入多个内容
import { MathUtils, StringUtils } from "./utils/GameUtils";
// 4. 重命名导入
import { HERO_STATS as HeroConfig } from "./config/GameConfig";
// 5. 导入所有内容
import * as Utils from "./utils/GameUtils";
4. 实际项目示例
让我们看看这个项目中的一些实际例子:
// src/solar/common/GlobalVars.ts
export default class GlobalVars {
static isDebug: boolean;
static init(debug: boolean) {
this.isDebug = debug;
}
}
// src/systems/AbilitySystem.ts
import GlobalVars from "../solar/common/GlobalVars";
import { DamageUtils } from "../utils/CombatUtils";
export class AbilitySystem {
castSpell(caster: unit, target: unit) {
// 使用导入的工具
if (GlobalVars.isDebug) {
console.log("施放技能");
}
let damage = DamageUtils.calculateSpellDamage(caster, target);
// ...
}
}
5. 模块组织示例
// src/config/index.ts
// 集中导出配置
export * from "./HeroConfig";
export * from "./ItemConfig";
export * from "./SpellConfig";
// src/systems/index.ts
// 集中导出系统
export * from "./HeroSystem";
export * from "./ItemSystem";
export * from "./CombatSystem";
// src/App.ts
// 统一导入
import {
HeroSystem,
ItemSystem,
CombatSystem
} from "./systems";
import {
HERO_CONFIG,
ITEM_CONFIG,
SPELL_CONFIG
} from "./config";
6. 模块的优势
- 代码组织
// 相关功能组织在一起
// src/systems/CombatSystem.ts
export class CombatSystem {
private damageCalculator: DamageCalculator;
private effectManager: EffectManager;
constructor() {
this.damageCalculator = new DamageCalculator();
this.effectManager = new EffectManager();
}
}
- 代码复用
// src/utils/CommonUtils.ts
export function delay(seconds: number): Promise<void> {
return new Promise(resolve => {
TimerStart(CreateTimer(), seconds, false, () => {
DestroyTimer(GetExpiredTimer());
resolve();
});
});
}
// 在其他文件中复用
import { delay } from "../utils/CommonUtils";
async function castDelayedSpell() {
await delay(1.0);
// 继续施法逻辑
}
依赖管理
// src/systems/SpellSystem.ts import { SPELL_CONFIG } from "../config"; import { EffectUtils } from "../utils/EffectUtils"; import { CombatUtils } from "../utils/CombatUtils"; export class SpellSystem { castFireball(caster: unit, target: unit) { let damage = SPELL_CONFIG.FIREBALL.damage; CombatUtils.dealDamage(caster, target, damage); EffectUtils.playSpellEffect("fireball", target); } }
7. 最佳实践
相关功能组织在一起
// src/systems/HeroSystem/ // index.ts export * from "./HeroCreator"; export * from "./HeroStats"; export * from "./HeroAbilities"; // HeroCreator.ts export class HeroCreator { /* ... */ } // HeroStats.ts export class HeroStats { /* ... */ } // HeroAbilities.ts export class HeroAbilities { /* ... */ }
清晰的导入导出
// 明确导入需要的内容 import { HeroCreator, HeroStats } from "./systems/HeroSystem"; import { ItemManager } from "./systems/ItemSystem"; import { GAME_CONFIG } from "./config";
避免循环依赖
// 好的做法:明确的依赖方向 // HeroSystem 依赖 ItemSystem import { ItemSystem } from "./ItemSystem"; // ItemSystem 不依赖 HeroSystem export class ItemSystem { // 通过参数传递 hero,而不是导入 HeroSystem useItem(hero: unit, item: item) { /* ... */ } }
默认导出(default export)和命名导出(named export)的区别
1. 默认导出 (Default Export)
- 每个模块只能有一个默认导出
- 导入时可以使用任意名称
- 通常用于导出模块的主要功能
例如在 App.ts 中的默认导出:
export default class App {
start() {
//先打印一个文本 让我们知道已运行到了TS入口代码
// DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, 'TS:App.start!');
//状态初始化
StateConfigInit();
StateInit();
abilitysss()
//测试区
new AppTest().start()
// if(idAndNum&&idAndNum.length>1&&idAndNum[0]&&idAndNum[1]){
// material[idAndNum[0]] = parseInt(idAndNum[1])
// }
}
}
导入默认导出:
import App from "./App"; // 可以使用任意名称
import MyApp from "./App"; // 同样有效
2. 命名导出 (Named Export)
- 一个模块可以有多个命名导出
- 导入时必须使用相同的名称(除非使用 as 重命名)
- 通常用于导出多个相关但独立的功能
例如:
// 命名导出
export const HERO_STATS = {
baseHealth: 500,
baseMana: 300
};
export function calculateDamage() {
// ...
}
export class HeroSystem {
// ...
}
导入命名导出:
import { HERO_STATS, calculateDamage, HeroSystem } from "./HeroSystem";
// 使用 as 重命名
import { HERO_STATS as HeroConfig } from "./HeroSystem";
3. 使用场景
使用默认导出的情况:
- 模块主要提供一个功能
- 模块代表一个类/组件
- 希望导入时有更灵活的命名
例如在你的代码中:
export default class GameStart {
static is: unit
static shangxian: number
static i: number = 0;
static j: number = 0;
static k: number = 0;
static l: number = 0;
static z1: number = 0;
static z2: number = 0;
static z3: number = 0;
static z4: number = 0;
static addmoster = 0
static chuangkou: string[] = [];
static zhuangbei: string[] = [
"a204",
"a205",
"a206",
"a207",
"a208",
"a209",
"a20a",
"a20b",
"a20c",
"a20d",
"a20e",
]
static config: {
start_text: string//初始文本
start_CreateUnitType: string//初始创建单位类型
pen_gives_bounty: number[]//获得奖励
} = {
start_text: "",//初始文本
start_CreateUnitType: "jz01",//初始创建单位类型
pen_gives_bounty: [],
}
constructor() {
this.Passive_trigger();
};
使用命名导出的情况:
- 模块提供多个相关但独立的功能
- 需要导出常量、工具函数等
- 希望强制使用特定的命名
4. 最佳实践
- 一个文件通常只应该有一个默认导出
- 相关的功能可以使用命名导出组织在一起
- 可以同时使用默认导出和命名导出,但要注意保持代码清晰
例如:
// 命名导出工具函数
export function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 默认导出主类
export default class GameSystem {
// ...
}
导入时:
import GameSystem, { delay } from "./GameSystem";
这样的组织方式使代码结构更清晰,更易于维护和使用。
是的,在同一个 TypeScript 文件(模块)中只能有一个默认导出(export default)。让我用例子来说明:
1. 正确的用法
// 正确:一个文件只有一个默认导出
export default class Hero {
// ...
}
// 可以有多个命名导出
export const HERO_STATS = { /* ... */ };
export function calculateDamage() { /* ... */ };
export class Equipment { /* ... */ };
2. 错误的用法
// ❌ 错误:不能有多个默认导出
export default class Hero {
// ...
}
export default class Weapon { // 这会导致编译错误
// ...
}
3. 实际项目中的例子
从你的项目代码中可以看到正确的使用方式:
// 正确:只有一个默认导出
export default class App {
start() {
StateConfigInit();
StateInit();
abilitysss()
new AppTest().start()
}
}
// 其他代码...
4. 如果需要导出多个内容,可以:
使用命名导出
// hero/index.ts export class Hero { /* ... */ } export class Weapon { /* ... */ } export class Armor { /* ... */ }
将多个相关项组合成一个对象或类后默认导出
// hero/index.ts class Hero { /* ... */ } class Weapon { /* ... */ } class Armor { /* ... */ } // 将相关内容组合后默认导出 export default { Hero, Weapon, Armor }
使用命名导出和默认导出的组合
// hero/index.ts export class Weapon { /* ... */ } export class Armor { /* ... */ } // 主要的类作为默认导出 export default class Hero { /* ... */ }
5. 导入方式的区别
// 导入默认导出
import Hero from './hero';
// 导入命名导出
import { Weapon, Armor } from './hero';
// 同时导入默认导出和命名导出
import Hero, { Weapon, Armor } from './hero';
// 导入组合的默认导出
import HeroSystem from './hero';
const { Hero, Weapon, Armor } = HeroSystem;
记住:
- 一个文件只能有一个
export default
- 一个文件可以有任意数量的命名导出(
export
) - 默认导出和命名导出可以共存在同一个文件中
- 选择使用默认导出还是命名导出应该基于模块的用途和组织结构