665 lines
18 KiB
Markdown
665 lines
18 KiB
Markdown
# 技术概念详解
|
||
|
||
本文档用通俗易懂的语言解释ECS框架中的关键技术概念,帮助开发者理解这些技术的作用和应用场景。
|
||
|
||
## 目录
|
||
|
||
- [ECS 架构基础](#ecs-架构基础)
|
||
- [性能优化技术](#性能优化技术)
|
||
- [事件系统](#事件系统)
|
||
- [实体管理](#实体管理)
|
||
|
||
## ECS 架构基础
|
||
|
||
### 什么是 ECS?
|
||
|
||
ECS (Entity-Component-System) 是一种编程架构模式,将游戏对象分解为三个独立的部分:
|
||
|
||
**传统面向对象方式:**
|
||
```typescript
|
||
// 传统继承方式 - 问题很多
|
||
class GameObject {
|
||
x: number; y: number;
|
||
render() { ... }
|
||
update() { ... }
|
||
}
|
||
|
||
class Player extends GameObject {
|
||
health: number;
|
||
shoot() { ... }
|
||
}
|
||
|
||
class Enemy extends Player { // 敌人需要射击但不需要玩家控制?
|
||
ai() { ... }
|
||
}
|
||
```
|
||
|
||
**ECS 方式:**
|
||
```typescript
|
||
// 数据和逻辑分离,灵活组合
|
||
const player = createEntity()
|
||
.add(PositionComponent) // 位置数据
|
||
.add(HealthComponent) // 生命值数据
|
||
.add(PlayerInputComponent) // 玩家输入标记
|
||
|
||
const enemy = createEntity()
|
||
.add(PositionComponent) // 复用位置数据
|
||
.add(HealthComponent) // 复用生命值数据
|
||
.add(AIComponent) // AI标记
|
||
|
||
// 系统处理具有特定组件的实体
|
||
MovementSystem.process([PositionComponent, VelocityComponent]);
|
||
```
|
||
|
||
### ECS 的优势
|
||
|
||
1. **灵活组合** - 像搭积木一样组装功能
|
||
2. **代码复用** - 组件可以在不同实体间复用
|
||
3. **性能优化** - 数据连续存储,缓存友好
|
||
4. **并行处理** - 系统间相互独立,可以并行执行
|
||
5. **易于测试** - 组件和系统可以独立测试
|
||
|
||
### 实际应用场景
|
||
|
||
**游戏开发中的例子:**
|
||
- **RPG游戏**:玩家、NPC、怪物都有位置和生命值,但只有玩家有输入组件
|
||
- **射击游戏**:子弹、玩家、敌人都有位置和碰撞体,但行为完全不同
|
||
- **策略游戏**:建筑、单位、资源都是实体,通过不同组件组合实现功能
|
||
|
||
## 性能优化技术
|
||
|
||
### 组件索引系统
|
||
|
||
**问题:** 没有索引时,查找组件需要遍历所有实体
|
||
```typescript
|
||
// 慢的方式:线性搜索 O(n)
|
||
function findEntitiesWithHealth() {
|
||
const result = [];
|
||
for (const entity of allEntities) { // 遍历10万个实体
|
||
if (entity.hasComponent(HealthComponent)) {
|
||
result.push(entity);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
```
|
||
|
||
**解决方案:** 建立索引,直接访问
|
||
```typescript
|
||
// 快的方式:索引查找 O(1)
|
||
const healthIndex = componentIndex.get(HealthComponent);
|
||
const entitiesWithHealth = healthIndex.getEntities(); // 直接获取
|
||
```
|
||
|
||
**应用场景:**
|
||
- 频繁查询特定组件的实体
|
||
- 大规模实体场景(数千到数万个实体)
|
||
- 实时游戏中的系统更新
|
||
|
||
### 索引类型选择指南
|
||
|
||
框架提供两种索引类型,选择合适的类型对性能至关重要:
|
||
|
||
#### 🔸 哈希索引 (Hash Index)
|
||
|
||
**适用场景:**
|
||
- 实体数量较多(> 1000个)
|
||
- 组件数据变化不频繁
|
||
- 需要快速查找特定实体
|
||
|
||
**优势:**
|
||
- 查询速度极快 O(1)
|
||
- 内存使用相对较少
|
||
- 适合大量实体
|
||
|
||
**缺点:**
|
||
- 添加/删除组件时有额外开销
|
||
- 不适合频繁变化的组件
|
||
|
||
```typescript
|
||
// 适合哈希索引的组件
|
||
componentIndex.setIndexType(PositionComponent, 'hash'); // 位置变化不频繁
|
||
componentIndex.setIndexType(HealthComponent, 'hash'); // 生命值组件稳定
|
||
componentIndex.setIndexType(PlayerComponent, 'hash'); // 玩家标记组件
|
||
```
|
||
|
||
#### 🔹 位图索引 (Bitmap Index)
|
||
|
||
**适用场景:**
|
||
- 组件频繁添加/删除
|
||
- 实体数量适中(< 10000个)
|
||
- 需要批量操作
|
||
|
||
**优势:**
|
||
- 添加/删除组件极快
|
||
- 批量查询效率高
|
||
- 内存访问模式好
|
||
|
||
**缺点:**
|
||
- 随实体数量增长,内存占用增加
|
||
- 稀疏数据时效率降低
|
||
|
||
```typescript
|
||
// 适合位图索引的组件
|
||
componentIndex.setIndexType(BuffComponent, 'bitmap'); // Buff经常添加删除
|
||
componentIndex.setIndexType(TemporaryComponent, 'bitmap'); // 临时组件
|
||
componentIndex.setIndexType(StateComponent, 'bitmap'); // 状态组件变化频繁
|
||
```
|
||
|
||
#### 选择决策表
|
||
|
||
| 考虑因素 | 哈希索引 (Hash) | 位图索引 (Bitmap) |
|
||
|---------|----------------|-------------------|
|
||
| **实体数量** | > 1,000 | < 10,000 |
|
||
| **组件变化频率** | 低频变化 | 高频变化 |
|
||
| **查询频率** | 高频查询 | 中等查询 |
|
||
| **内存使用** | 较少 | 随实体数增加 |
|
||
| **批量操作** | 一般 | 优秀 |
|
||
|
||
#### 快速决策流程
|
||
|
||
**第一步:判断组件变化频率**
|
||
- 组件经常添加/删除? → 选择 **位图索引**
|
||
- 组件相对稳定? → 继续第二步
|
||
|
||
**第二步:判断实体数量**
|
||
- 实体数量 > 1000? → 选择 **哈希索引**
|
||
- 实体数量 < 1000? → 选择 **位图索引**
|
||
|
||
**第三步:特殊情况**
|
||
- 需要频繁批量操作? → 选择 **位图索引**
|
||
- 内存使用很重要? → 选择 **哈希索引**
|
||
|
||
#### 实际游戏中的选择示例
|
||
|
||
**射击游戏:**
|
||
```typescript
|
||
// 稳定组件用哈希索引
|
||
componentIndex.setIndexType(PositionComponent, 'hash'); // 实体位置稳定存在
|
||
componentIndex.setIndexType(HealthComponent, 'hash'); // 生命值组件持续存在
|
||
componentIndex.setIndexType(WeaponComponent, 'hash'); // 武器组件不常变化
|
||
|
||
// 变化组件用位图索引
|
||
componentIndex.setIndexType(BuffComponent, 'bitmap'); // Buff频繁添加删除
|
||
componentIndex.setIndexType(ReloadingComponent, 'bitmap'); // 装弹状态临时组件
|
||
```
|
||
|
||
**策略游戏:**
|
||
```typescript
|
||
// 大量单位,核心组件用哈希
|
||
componentIndex.setIndexType(UnitComponent, 'hash'); // 单位类型稳定
|
||
componentIndex.setIndexType(OwnerComponent, 'hash'); // 所属玩家稳定
|
||
|
||
// 状态组件用位图
|
||
componentIndex.setIndexType(SelectedComponent, 'bitmap'); // 选中状态常变化
|
||
componentIndex.setIndexType(MovingComponent, 'bitmap'); // 移动状态变化
|
||
componentIndex.setIndexType(AttackingComponent, 'bitmap'); // 攻击状态临时
|
||
```
|
||
|
||
**RPG游戏:**
|
||
```typescript
|
||
// 角色核心属性用哈希
|
||
componentIndex.setIndexType(StatsComponent, 'hash'); // 属性组件稳定
|
||
componentIndex.setIndexType(InventoryComponent, 'hash'); // 背包组件稳定
|
||
componentIndex.setIndexType(LevelComponent, 'hash'); // 等级组件稳定
|
||
|
||
// 临时状态用位图
|
||
componentIndex.setIndexType(StatusEffectComponent, 'bitmap'); // 状态效果变化
|
||
componentIndex.setIndexType(QuestComponent, 'bitmap'); // 任务状态变化
|
||
componentIndex.setIndexType(CombatComponent, 'bitmap'); // 战斗状态临时
|
||
```
|
||
|
||
#### ❌ 常见选择错误
|
||
|
||
**错误示例1:大量实体使用位图索引**
|
||
```typescript
|
||
// ❌ 错误:10万个单位用位图索引,内存爆炸
|
||
const entityCount = 100000;
|
||
componentIndex.setIndexType(UnitComponent, 'bitmap'); // 内存占用过大!
|
||
|
||
// 正确:大量实体用哈希索引
|
||
componentIndex.setIndexType(UnitComponent, 'hash');
|
||
```
|
||
|
||
**错误示例2:频繁变化组件用哈希索引**
|
||
```typescript
|
||
// ❌ 错误:Buff频繁添加删除,哈希索引效率低
|
||
componentIndex.setIndexType(BuffComponent, 'hash'); // 添加删除慢!
|
||
|
||
// 正确:变化频繁的组件用位图索引
|
||
componentIndex.setIndexType(BuffComponent, 'bitmap');
|
||
```
|
||
|
||
**错误示例3:不考虑实际使用场景**
|
||
```typescript
|
||
// ❌ 错误:所有组件都用同一种索引
|
||
componentIndex.setIndexType(PositionComponent, 'hash');
|
||
componentIndex.setIndexType(BuffComponent, 'hash'); // 应该用bitmap
|
||
componentIndex.setIndexType(TemporaryComponent, 'hash'); // 应该用bitmap
|
||
|
||
// 正确:根据组件特性选择
|
||
componentIndex.setIndexType(PositionComponent, 'hash'); // 稳定组件
|
||
componentIndex.setIndexType(BuffComponent, 'bitmap'); // 变化组件
|
||
componentIndex.setIndexType(TemporaryComponent, 'bitmap'); // 临时组件
|
||
```
|
||
|
||
### Archetype 系统
|
||
|
||
**什么是 Archetype?**
|
||
Archetype(原型)是具有相同组件组合的实体分组。
|
||
|
||
**没有 Archetype 的问题:**
|
||
```typescript
|
||
// 每次都要检查每个实体的组件组合
|
||
for (const entity of allEntities) {
|
||
if (entity.has(Position) && entity.has(Velocity) && !entity.has(Frozen)) {
|
||
// 处理移动
|
||
}
|
||
}
|
||
```
|
||
|
||
**Archetype 的解决方案:**
|
||
```typescript
|
||
// 实体按组件组合自动分组
|
||
const movableArchetype = [Position, Velocity, !Frozen];
|
||
const movableEntities = archetypeSystem.getEntities(movableArchetype);
|
||
// 直接处理,无需逐个检查
|
||
```
|
||
|
||
**应用场景:**
|
||
- 大量实体的游戏(RTS、MMO)
|
||
- 频繁的实体查询操作
|
||
- 批量处理相同类型的实体
|
||
|
||
### 脏标记系统
|
||
|
||
**什么是脏标记?**
|
||
脏标记(Dirty Tracking)追踪哪些数据发生了变化,避免处理未变化的数据。
|
||
|
||
**没有脏标记的问题:**
|
||
```typescript
|
||
// 每帧都重新计算所有实体,即使它们没有移动
|
||
function renderSystem() {
|
||
for (const entity of entities) {
|
||
updateRenderPosition(entity); // 浪费计算
|
||
updateRenderRotation(entity); // 浪费计算
|
||
updateRenderScale(entity); // 浪费计算
|
||
}
|
||
}
|
||
```
|
||
|
||
**脏标记的解决方案:**
|
||
```typescript
|
||
// 只处理发生变化的实体
|
||
function renderSystem() {
|
||
const dirtyEntities = dirtyTracking.getDirtyEntities();
|
||
for (const entity of dirtyEntities) {
|
||
if (dirtyTracking.isDirty(entity, PositionComponent)) {
|
||
updateRenderPosition(entity); // 只在需要时计算
|
||
}
|
||
if (dirtyTracking.isDirty(entity, RotationComponent)) {
|
||
updateRenderRotation(entity);
|
||
}
|
||
}
|
||
dirtyTracking.clearDirtyFlags();
|
||
}
|
||
```
|
||
|
||
**应用场景:**
|
||
- 渲染系统优化(只更新变化的物体)
|
||
- 物理系统优化(只计算移动的物体)
|
||
- UI更新优化(只刷新变化的界面元素)
|
||
- 网络同步优化(只发送变化的数据)
|
||
|
||
**实际例子:**
|
||
```typescript
|
||
// 游戏中的应用
|
||
class MovementSystem {
|
||
process() {
|
||
// 玩家移动时标记为脏
|
||
if (playerInput.moved) {
|
||
dirtyTracking.markDirty(player, PositionComponent);
|
||
}
|
||
|
||
// 静止的敌人不会被标记为脏,渲染系统会跳过它们
|
||
}
|
||
}
|
||
```
|
||
|
||
## 事件系统
|
||
|
||
### 类型安全事件
|
||
|
||
**传统事件的问题:**
|
||
```typescript
|
||
// 类型不安全,容易出错
|
||
eventEmitter.emit('player_died', playerData);
|
||
eventEmitter.on('player_dead', handler); // 事件名拼写错误!
|
||
```
|
||
|
||
**类型安全事件的解决方案:**
|
||
```typescript
|
||
// 编译时检查,避免错误
|
||
enum GameEvents {
|
||
PLAYER_DIED = 'player:died',
|
||
LEVEL_COMPLETED = 'level:completed'
|
||
}
|
||
|
||
eventBus.emit(GameEvents.PLAYER_DIED, { playerId: 123 });
|
||
eventBus.on(GameEvents.PLAYER_DIED, (data) => {
|
||
// data 类型自动推断
|
||
});
|
||
```
|
||
|
||
### 事件装饰器
|
||
|
||
**什么是装饰器?**
|
||
装饰器让你用简单的语法自动注册事件监听器。
|
||
|
||
**传统方式:**
|
||
```typescript
|
||
class GameManager {
|
||
constructor() {
|
||
// 手动注册事件
|
||
eventBus.on('entity:created', this.onEntityCreated.bind(this));
|
||
eventBus.on('entity:destroyed', this.onEntityDestroyed.bind(this));
|
||
eventBus.on('component:added', this.onComponentAdded.bind(this));
|
||
}
|
||
|
||
onEntityCreated(data) { ... }
|
||
onEntityDestroyed(data) { ... }
|
||
onComponentAdded(data) { ... }
|
||
}
|
||
```
|
||
|
||
**装饰器方式:**
|
||
```typescript
|
||
class GameManager {
|
||
@EventHandler('entity:created')
|
||
onEntityCreated(data) { ... } // 自动注册
|
||
|
||
@EventHandler('entity:destroyed')
|
||
onEntityDestroyed(data) { ... } // 自动注册
|
||
|
||
@EventHandler('component:added')
|
||
onComponentAdded(data) { ... } // 自动注册
|
||
}
|
||
```
|
||
|
||
**应用场景:**
|
||
- 游戏状态管理
|
||
- UI更新响应
|
||
- 音效播放触发
|
||
- 成就系统检查
|
||
|
||
## 实体管理
|
||
|
||
### 实体生命周期
|
||
|
||
**创建实体的不同方式:**
|
||
```typescript
|
||
// 单个创建 - 适用于重要实体
|
||
const player = scene.createEntity("Player");
|
||
|
||
// 批量创建 - 适用于大量相似实体
|
||
const bullets = scene.createEntities(100, "Bullet");
|
||
|
||
// 延迟创建 - 避免性能峰值
|
||
// 分批创建大量实体以避免单帧卡顿
|
||
for (let i = 0; i < 100; i++) {
|
||
setTimeout(() => {
|
||
const batch = scene.createEntities(10, "Enemy");
|
||
// 配置批次实体...
|
||
}, i * 16); // 每16ms创建一批
|
||
}
|
||
```
|
||
|
||
### 查询系统
|
||
|
||
**流式API的优势:**
|
||
```typescript
|
||
// 传统方式:复杂的条件判断
|
||
const result = [];
|
||
for (const entity of entities) {
|
||
if (entity.has(Position) &&
|
||
entity.has(Velocity) &&
|
||
!entity.has(Frozen) &&
|
||
entity.tag === EntityTag.ENEMY) {
|
||
result.push(entity);
|
||
}
|
||
}
|
||
|
||
// 流式API:清晰表达意图
|
||
const result = entityManager
|
||
.query()
|
||
.withAll(Position, Velocity)
|
||
.withNone(Frozen)
|
||
.withTag(EntityTag.ENEMY)
|
||
.execute();
|
||
```
|
||
|
||
### 批量操作
|
||
|
||
**为什么需要批量操作?**
|
||
```typescript
|
||
// 慢的方式:逐个处理
|
||
for (let i = 0; i < 1000; i++) {
|
||
const bullet = createEntity();
|
||
bullet.addComponent(new PositionComponent());
|
||
bullet.addComponent(new VelocityComponent());
|
||
}
|
||
|
||
// 快的方式:批量处理
|
||
const bullets = scene.createEntities(1000, "Bullet");
|
||
bullets.forEach(bullet => {
|
||
bullet.addComponent(new PositionComponent());
|
||
bullet.addComponent(new VelocityComponent());
|
||
});
|
||
```
|
||
|
||
**应用场景:**
|
||
- 生成大量子弹/粒子
|
||
- 加载关卡时创建大量实体
|
||
- 清理场景时删除大量实体
|
||
|
||
## 性能建议
|
||
|
||
### 什么时候使用这些优化?
|
||
|
||
| 实体数量 | 推荐配置 | 说明 |
|
||
|---------|---------|------|
|
||
| < 1,000 | 默认配置 | 简单场景,不需要特殊优化 |
|
||
| 1,000 - 10,000 | 启用组件索引 | 中等规模,索引提升查询速度 |
|
||
| 10,000 - 50,000 | 启用Archetype | 大规模场景,分组优化 |
|
||
| > 50,000 | 全部优化 | 超大规模,需要所有优化技术 |
|
||
|
||
### 常见使用误区
|
||
|
||
**错误:过度优化**
|
||
```typescript
|
||
// 不要在小项目中使用所有优化
|
||
const entityManager = new EntityManager();
|
||
entityManager.enableAllOptimizations(); // 小项目不需要
|
||
```
|
||
|
||
**正确:按需优化**
|
||
```typescript
|
||
// 根据实际需求选择优化
|
||
if (entityCount > 10000) {
|
||
entityManager.enableArchetypeSystem();
|
||
}
|
||
if (hasFrequentQueries) {
|
||
entityManager.enableComponentIndex();
|
||
}
|
||
```
|
||
|
||
## 总结
|
||
|
||
这些技术概念可能看起来复杂,但它们解决的都是实际开发中的具体问题:
|
||
|
||
1. **ECS架构** - 让代码更灵活、可维护
|
||
2. **组件索引** - 让查询更快速
|
||
3. **Archetype系统** - 让批量操作更高效
|
||
4. **脏标记系统** - 让更新更智能
|
||
5. **事件系统** - 让组件间通信更安全
|
||
6. **实体管理** - 让大规模场景成为可能
|
||
|
||
从简单的场景开始,随着项目复杂度增加,逐步引入这些优化技术。
|
||
|
||
## 框架类型系统
|
||
|
||
### TypeScript接口设计
|
||
|
||
ECS框架采用了精简的TypeScript接口设计,提供类型安全保障的同时保持实现的灵活性。
|
||
|
||
#### 核心接口
|
||
|
||
**IComponent接口**
|
||
```typescript
|
||
interface IComponent {
|
||
readonly id: number;
|
||
enabled: boolean;
|
||
updateOrder: number;
|
||
|
||
onAddedToEntity(): void;
|
||
onRemovedFromEntity(): void;
|
||
onEnabled(): void;
|
||
onDisabled(): void;
|
||
update(): void;
|
||
}
|
||
```
|
||
- 定义所有组件的基本契约
|
||
- Component基类实现此接口
|
||
- 确保组件生命周期方法的一致性
|
||
|
||
**ISystemBase接口**
|
||
```typescript
|
||
interface ISystemBase {
|
||
readonly systemName: string;
|
||
readonly entities: readonly any[];
|
||
updateOrder: number;
|
||
enabled: boolean;
|
||
|
||
initialize(): void;
|
||
update(): void;
|
||
lateUpdate?(): void;
|
||
}
|
||
```
|
||
- 为EntitySystem类提供类型约束
|
||
- 定义系统的核心执行方法
|
||
- 支持可选的延迟更新
|
||
|
||
**IEventBus接口**
|
||
```typescript
|
||
interface IEventBus {
|
||
emit<T>(eventType: string, data: T): void;
|
||
emitAsync<T>(eventType: string, data: T): Promise<void>;
|
||
on<T>(eventType: string, handler: (data: T) => void, config?: IEventListenerConfig): string;
|
||
// ... 其他事件方法
|
||
}
|
||
```
|
||
- 提供类型安全的事件系统契约
|
||
- 支持同步和异步事件处理
|
||
- EventBus类完整实现此接口
|
||
|
||
#### 事件数据接口
|
||
|
||
**事件数据层次结构**
|
||
```typescript
|
||
// 基础事件数据
|
||
interface IEventData {
|
||
timestamp: number;
|
||
source?: string;
|
||
eventId?: string;
|
||
}
|
||
|
||
// 实体相关事件
|
||
interface IEntityEventData extends IEventData {
|
||
entityId: number;
|
||
entityName?: string;
|
||
entityTag?: string;
|
||
}
|
||
|
||
// 组件相关事件
|
||
interface IComponentEventData extends IEntityEventData {
|
||
componentType: string;
|
||
component?: IComponent;
|
||
}
|
||
```
|
||
- 清晰的继承层次
|
||
- 类型安全的事件数据传递
|
||
- 便于事件处理器的实现
|
||
|
||
#### 类型别名
|
||
|
||
**ComponentType<T>**
|
||
```typescript
|
||
type ComponentType<T extends IComponent = IComponent> = new (...args: any[]) => T;
|
||
```
|
||
- 用于类型安全的组件操作
|
||
- 支持泛型约束
|
||
- 广泛用于实体和查询系统
|
||
|
||
### 设计原则
|
||
|
||
#### 1. 接口简化原则
|
||
- 只保留实际使用的接口
|
||
- 移除了未使用的复杂接口(如IEntityManager、IEntityQueryBuilder等)
|
||
- 减少认知负担,提高开发效率
|
||
|
||
#### 2. 实现灵活性原则
|
||
- 接口作为类型约束而非强制实现
|
||
- 允许具体类有更丰富的实现
|
||
- 保持向后兼容性
|
||
|
||
#### 3. 类型安全原则
|
||
- 编译时类型检查
|
||
- 泛型支持提供精确的类型推断
|
||
- 事件系统的完整类型安全
|
||
|
||
### 使用指南
|
||
|
||
#### 在项目中使用接口
|
||
```typescript
|
||
// 作为类型约束
|
||
function processComponent<T extends IComponent>(component: T) {
|
||
if (component.enabled) {
|
||
component.update();
|
||
}
|
||
}
|
||
|
||
// 作为参数类型
|
||
function registerSystem(system: ISystemBase) {
|
||
scene.addEntityProcessor(system);
|
||
}
|
||
|
||
// 作为泛型约束
|
||
function getComponent<T extends IComponent>(type: ComponentType<T>): T | null {
|
||
return entity.getComponent(type);
|
||
}
|
||
```
|
||
|
||
#### 扩展框架接口
|
||
```typescript
|
||
// 如果需要扩展组件接口
|
||
interface IAdvancedComponent extends IComponent {
|
||
priority: number;
|
||
category: string;
|
||
}
|
||
|
||
class AdvancedComponent extends Component implements IAdvancedComponent {
|
||
public priority: number = 0;
|
||
public category: string = "default";
|
||
|
||
// 实现基础接口方法
|
||
}
|
||
```
|
||
|
||
### 接口维护
|
||
|
||
当前的接口设计已经过精心清理,包含:
|
||
- **12个核心接口** - 涵盖组件、系统、事件等核心概念
|
||
- **0个冗余接口** - 移除了所有未使用的接口定义
|
||
- **完整的类型覆盖** - 为所有主要功能提供类型支持
|
||
|
||
这种设计确保了框架的类型安全性,同时保持了代码的简洁性和可维护性。 |