1. 泛型概述
泛型将类型作为参数,使函数、类或接口能够处理多种类型而不丢失类型信息。与 C# 泛型概念相似,但 TypeScript 泛型在编译后会被擦除,仅作用于类型检查阶段。
tsx
function identity<T>(value: T): T {
return value;
}
const num = identity<number>(42); // num: number
const str = identity<string>("text"); // str: string
const inferred = identity(true); // inferred: boolean(类型推断)
2. 泛型函数
泛型函数可定义类型参数,用于约束参数与返回值之间的关系。
tsx
function firstElement<T>(arr: T[]): T | undefined {
return arr.length > 0 ? arr[0] : undefined;
}
const numbers = [1, 2, 3];
const firstNum = firstElement(numbers); // firstNum: number | undefined
const strings = ["a", "b"];
const firstStr = firstElement(strings); // firstStr: string | undefined
2.1 游戏场景:配置表加载函数
tsx
interface ConfigRow {
id: number;
}
function loadTable<T extends ConfigRow>(tableName: string): T[] {
// 模拟读取 JSON 或解析 CSV 返回数据
const rawData = fetchTableData(tableName);
return rawData as T[];
}
interface MonsterRow extends ConfigRow {
name: string;
health: number;
attack: number;
}
interface SkillRow extends ConfigRow {
name: string;
manaCost: number;
cooldown: number;
}
const monsters = loadTable<MonsterRow>("Monster");
const skills = loadTable<SkillRow>("Skill");
// 类型安全:访问属性有完整提示
monsters[0].health;
skills[0].cooldown;
2.2 泛型约束
extends 关键字约束类型参数必须满足特定形状,类似于 C# 的 where T : class 或 where T : struct。
tsx
interface HasId {
id: number;
}
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
const monster = findById(monsters, 101); // monster: MonsterRow | undefined
多重约束通过交叉类型实现:
tsx
interface Identifiable { id: number; }
interface Nameable { name: string; }
function processEntity<T extends Identifiable & Nameable>(entity: T): void {
console.log(`Entity ${entity.id}: ${entity.name}`);
}
3. 泛型类
泛型类允许类成员共享类型参数,典型应用包括数据结构与工厂类。
tsx
class ObjectPool<T> {
private pool: T[] = [];
private createFn: () => T;
private resetFn: (obj: T) => void;
constructor(createFn: () => T, resetFn: (obj: T) => void, initialSize: number = 0) {
this.createFn = createFn;
this.resetFn = resetFn;
for (let i = 0; i < initialSize; i++) {
this.pool.push(this.createFn());
}
}
public acquire(): T {
if (this.pool.length > 0) {
return this.pool.pop()!;
}
return this.createFn();
}
public release(obj: T): void {
this.resetFn(obj);
this.pool.push(obj);
}
}
// 使用示例:子弹对象池
interface Bullet {
active: boolean;
position: [number, number];
velocity: [number, number];
}
const bulletPool = new ObjectPool<Bullet>(
() => ({ active: false, position: [0, 0], velocity: [0, 0] }),
(b) => { b.active = false; b.position = [0, 0]; b.velocity = [0, 0]; },
20
);
const bullet = bulletPool.acquire();
bullet.active = true;
// ... 使用后回收
bulletPool.release(bullet);
4. 泛型接口
接口可定义泛型参数,用于描述灵活的数据结构。
tsx
interface Repository<T> {
find(id: number): T | undefined;
findAll(): T[];
save(entity: T): void;
delete(id: number): boolean;
}
class MonsterRepository implements Repository<MonsterRow> {
private data: Map<number, MonsterRow> = new Map();
find(id: number): MonsterRow | undefined {
return this.data.get(id);
}
findAll(): MonsterRow[] {
return Array.from(this.data.values());
}
save(entity: MonsterRow): void {
this.data.set(entity.id, entity);
}
delete(id: number): boolean {
return this.data.delete(id);
}
}
5. keyof 与索引访问类型
5.1 keyof 类型运算符
keyof T 返回类型 T 所有键名组成的字面量联合类型。
tsx
interface PlayerData {
id: number;
name: string;
level: number;
exp: number;
}
type PlayerKey = keyof PlayerData; // "id" | "name" | "level" | "exp"
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const player: PlayerData = { id: 1, name: "Hero", level: 10, exp: 1500 };
const level = getProperty(player, "level"); // level: number
const name = getProperty(player, "name"); // name: string
// const invalid = getProperty(player, "health"); // 错误:health 不是 PlayerData 的属性
游戏场景中的应用:安全的属性修改器。
tsx
function modifyStat<T, K extends keyof T>(
target: T,
stat: K,
delta: T[K] extends number ? number : never
): void {
if (typeof target[stat] === "number") {
(target[stat] as number) += delta;
}
}
modifyStat(player, "level", 1); // 合法
// modifyStat(player, "name", 1); // 错误:name 非 number 类型
5.2 索引访问类型
T[K] 获取类型 T 中键 K 对应的属性类型。
tsx
type PlayerNameType = PlayerData["name"]; // string
type PlayerNumericStats = PlayerData["level" | "exp"]; // number
6. 内置工具类型
TypeScript 提供一组实用类型,基于泛型与映射类型实现,用于常见类型变换操作。
6.1 Partial
将类型 T 的所有属性变为可选。常用于对象更新、Buff 临时修改场景。
tsx
interface BuffEffect {
attackBonus: number;
defenseBonus: number;
speedMultiplier: number;
}
function applyBuff(target: PlayerData, buff: Partial<BuffEffect>): void {
// 仅应用传入的加成,未传入的保持原值
if (buff.attackBonus) { /* ... */ }
if (buff.speedMultiplier) { /* ... */ }
}
applyBuff(player, { attackBonus: 15 }); // 仅传入部分属性
6.2 Readonly
将类型 T 的所有属性变为只读。适用于不可变配置、常量数据。
tsx
interface GameConfig {
maxPlayers: number;
tickRate: number;
mapSize: number;
}
const config: Readonly<GameConfig> = {
maxPlayers: 50,
tickRate: 30,
mapSize: 1024
};
// config.maxPlayers = 60; // 错误:只读属性不可修改
6.3 Pick<T, K>
从类型 T 中选取键 K 的子集构造新类型。
tsx
interface CharacterFull {
id: number;
name: string;
health: number;
mana: number;
position: [number, number];
inventory: Item[];
}
type NetworkSyncData = Pick<CharacterFull, "id" | "position" | "health">;
function syncState(data: NetworkSyncData): void {
// 仅同步必要字段,减少带宽
console.log(`Sync ID ${data.id}: pos=(${data.position}), hp=${data.health}`);
}
6.4 Omit<T, K>
从类型 T 中排除键 K 构造新类型,与 Pick 互补。
tsx
type CharacterWithoutInventory = Omit<CharacterFull, "inventory">;
function serializeCharacter(char: CharacterWithoutInventory): string {
// 序列化时忽略背包数据
return JSON.stringify(char);
}
6.5 Record<K, V>
构造键类型为 K、值类型为 V 的对象类型。常用于字典、映射表。
tsx
type SkillId = 101 | 102 | 103;
type SkillNameMap = Record<SkillId, string>;
const skillNames: SkillNameMap = {
101: "Slash",
102: "Dash",
103: "Fireball"
};
// 更常见的用法:字符串键映射到配置类型
type ConfigDictionary = Record<string, MonsterRow>;
const monsterDict: ConfigDictionary = {};
monsterDict["goblin"] = { id: 1, name: "Goblin", health: 30, attack: 8 };
6.6 Exclude<T, U> 与 Extract<T, U>
从联合类型中排除或提取特定成员。
tsx
type AllStatus = "Idle" | "Run" | "Attack" | "Death" | "Stun";
type MovableStatus = Exclude<AllStatus, "Death" | "Stun">; // "Idle" | "Run" | "Attack"
type UncontrollableStatus = Extract<AllStatus, "Death" | "Stun">; // "Death" | "Stun"
function canMove(status: AllStatus): status is MovableStatus {
return status !== "Death" && status !== "Stun";
}
6.7 ReturnType
获取函数类型 T 的返回值类型。
tsx
function createEnemy(): { id: number; type: string } {
return { id: 1, type: "Goblin" };
}
type EnemyInstance = ReturnType<typeof createEnemy>; // { id: number; type: string; }
6.8 Parameters
获取函数类型 T 的参数类型元组。
tsx
function dealDamage(target: Character, amount: number, critical: boolean): void { }
type DamageParams = Parameters<typeof dealDamage>; // [Character, number, boolean]
7. 自定义工具类型
结合泛型、映射类型与条件类型可创建领域专用的工具类型。
7.1 可空类型
tsx
type Nullable<T> = { [P in keyof T]: T[P] | null };
interface SaveData {
lastCheckpoint: string;
playTime: number;
}
type NullableSaveData = Nullable<SaveData>;
// { lastCheckpoint: string | null; playTime: number | null; }
7.2 深度只读
tsx
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
interface NestedConfig {
graphics: {
resolution: [number, number];
quality: number;
};
audio: {
volume: number;
};
}
const settings: DeepReadonly<NestedConfig> = {
graphics: { resolution: [1920, 1080], quality: 5 },
audio: { volume: 0.8 }
};
// settings.graphics.quality = 3; // 错误:深层属性亦为只读
8. 条件类型简介
条件类型根据类型关系选择分支,语法为 T extends U ? X : Y。
tsx
type IsNumber<T> = T extends number ? true : false;
type A = IsNumber<string>; // false
type B = IsNumber<42>; // true
// 提取函数返回值为 Promise 的实际类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Result1 = UnwrapPromise<Promise<number>>; // number
type Result2 = UnwrapPromise<string>; // string
infer 关键字在条件类型分支中声明待推断的类型变量,用于提取类型信息。
8.1 游戏场景:技能效果类型推断
tsx
interface DamageEffect { type: "damage"; value: number; }
interface HealEffect { type: "heal"; value: number; }
interface BuffEffect { type: "buff"; buffId: number; duration: number; }
type SkillEffect = DamageEffect | HealEffect | BuffEffect;
// 根据 type 字段提取对应效果类型
type EffectByType<T extends SkillEffect["type"]> = Extract<SkillEffect, { type: T }>;
type DamageOnly = EffectByType<"damage">; // DamageEffect
type HealOnly = EffectByType<"heal">; // HealEffect
9. 游戏场景综合示例
9.1 泛型事件系统
tsx
type EventMap = {
"PlayerDied": { playerId: number; killerId?: number };
"ItemPickedUp": { itemId: number; quantity: number };
"LevelUp": { playerId: number; newLevel: number };
};
class TypedEventBus {
private listeners: Map<keyof EventMap, Function[]> = new Map();
public on<K extends keyof EventMap>(
event: K,
handler: (data: EventMap[K]) => void
): void {
const handlers = this.listeners.get(event) || [];
handlers.push(handler);
this.listeners.set(event, handlers);
}
public emit<K extends keyof EventMap>(event: K, data: EventMap[K]): void {
const handlers = this.listeners.get(event) || [];
handlers.forEach(h => h(data));
}
}
const bus = new TypedEventBus();
bus.on("PlayerDied", (data) => {
console.log(`Player ${data.playerId} died, killer: ${data.killerId ?? "unknown"}`);
// data 自动推断为 { playerId: number; killerId?: number }
});
bus.emit("PlayerDied", { playerId: 1, killerId: 5 });
9.2 泛型状态机
tsx
class StateMachine<TState extends string, TContext> {
private currentState: TState;
private transitions: Map<TState, Partial<Record<TState, (ctx: TContext) => boolean>>> = new Map();
constructor(initialState: TState) {
this.currentState = initialState;
}
public addTransition(
from: TState,
to: TState,
condition?: (ctx: TContext) => boolean
): void {
if (!this.transitions.has(from)) {
this.transitions.set(from, {});
}
this.transitions.get(from)![to] = condition || (() => true);
}
public tryTransition(to: TState, context: TContext): boolean {
const fromTransitions = this.transitions.get(this.currentState);
if (!fromTransitions) return false;
const condition = fromTransitions[to];
if (condition && condition(context)) {
this.currentState = to;
return true;
}
return false;
}
public get state(): TState {
return this.currentState;
}
}
// 使用示例:敌人 AI 状态机
type EnemyState = "Idle" | "Patrol" | "Chase" | "Attack" | "Return";
interface EnemyContext {
targetInSight: boolean;
distanceToTarget: number;
health: number;
}
const ai = new StateMachine<EnemyState, EnemyContext>("Idle");
ai.addTransition("Idle", "Patrol");
ai.addTransition("Patrol", "Chase", ctx => ctx.targetInSight);
ai.addTransition("Chase", "Attack", ctx => ctx.distanceToTarget < 5);
ai.addTransition("Attack", "Chase", ctx => ctx.distanceToTarget >= 5);
ai.addTransition("Chase", "Return", ctx => !ctx.targetInSight);
ai.addTransition("Return", "Idle", ctx => ctx.distanceToTarget > 20);
10. 本篇小结
- 泛型提供类型参数化能力,在函数、类、接口中实现类型安全的复用逻辑。
extends约束类型参数,keyof获取对象键名联合类型。- 内置工具类型(
Partial、Readonly、Pick、Omit、Record等)覆盖常见类型变换需求。 - 条件类型与
infer实现高级类型推断,适用于复杂场景。 - 游戏开发中泛型广泛应用于配置加载、对象池、事件系统与状态机,确保编译期类型正确性。
下一篇将讨论模块系统与命名空间,介绍如何组织大型 TypeScript 项目的代码结构。
参考资源