3、TypeScript 快速入门:变量声明、函数、类与接口、模块化开发基础
好,咱们直接进入正题。做抖音小游戏,TypeScript 几乎是绕不开的。为什么?因为 Cocos Creator 官方推荐,而且大型项目里 JavaScript 那种“松散”的写法,后期维护起来真的会让人崩溃。我最早做小游戏时,用纯 JS 写了个几千行的逻辑,结果改一个变量名,满世界找引用,那叫一个酸爽。
TypeScript 说白了就是 JavaScript 的“超集”,给它加了一套类型系统。你写的时候多花几分钟,后面能省下几小时的调试时间。值不值?你自己算算这笔账。
变量声明:let、const 与类型注解
先说说变量声明。ES6 之后,我们基本不用 var 了。我个人习惯,能用 const 就用 const,只有确定要重新赋值的才用 let。为什么?因为不可变性让代码更安全,你想想看,一个变量从头到尾都不变,那它的值就很好追踪。
// 类型注解:变量名后面加冒号和类型
let playerName: string = '勇者';
let playerLevel: number = 1;
let isAlive: boolean = true;
// 常量,类型可以自动推断
const MAX_HP: number = 100;
const GAME_TITLE = '抖音小游戏'; // 这里 TS 会自动推断为 string 类型
嗯,这里要注意:TypeScript 的类型推断很智能。如果你写 let score = 0,它会自动把 score 推断为 number 类型。你后面再给它赋个字符串,编译器就会报错。我在项目中遇到过,有人图省事不写类型注解,结果后面不小心把分数赋成了字符串 '100分',导致整个计分系统崩溃。所以,明确写出类型注解,既是给编译器看,也是给未来的自己看。
typeof 操作符在运行时检查。但更推荐的做法是,在声明时就明确类型。
函数:参数类型与返回值类型
函数是游戏逻辑的基本单元。在 TypeScript 里,函数的参数和返回值都可以加类型注解。这有什么好处?调用函数时,你一眼就能知道该传什么参数、会得到什么结果。
// 普通函数:参数和返回值都标注类型
function calculateDamage(attack: number, defense: number): number {
let damage = attack - defense;
return damage > 0 ? damage : 0;
}
// 箭头函数(我更喜欢这种写法,简洁)
const heal = (hp: number, amount: number): number => {
return Math.min(hp + amount, MAX_HP);
};
// 可选参数:用问号表示
function createPlayer(name: string, level?: number): void {
console.log(`创建玩家:${name},等级:${level ?? 1}`);
}
我曾经在写一个技能系统时,忘记给函数参数加类型,结果传了个对象进去,函数内部直接 undefined 报错。加了类型注解后,编辑器直接标红,一秒定位问题。说白了,类型注解就是你的“代码说明书”。
类与接口:面向对象的核心
小游戏开发中,类用得特别多。比如玩家、敌人、道具,都可以抽象成类。而接口,就是定义“这个类应该长什么样”的契约。
// 接口:定义结构
interface IPlayer {
name: string;
hp: number;
level: number;
attack(target: IEnemy): void;
}
interface IEnemy {
name: string;
hp: number;
damage: number;
}
// 类实现接口
class Player implements IPlayer {
// 公有属性
public name: string;
public hp: number;
public level: number;
// 私有属性(外部不能直接访问)
private exp: number = 0;
constructor(name: string) {
this.name = name;
this.hp = 100;
this.level = 1;
}
public attack(target: IEnemy): void {
console.log(`${this.name} 攻击了 ${target.name}`);
target.hp -= 10;
}
// 私有方法
private gainExp(amount: number): void {
this.exp += amount;
if (this.exp >= 100) {
this.levelUp();
}
}
private levelUp(): void {
this.level++;
this.exp = 0;
console.log(`升级!当前等级:${this.level}`);
}
}
// 使用
const player = new Player('抖音玩家');
const enemy: IEnemy = { name: '小怪', hp: 50, damage: 5 };
player.attack(enemy);
你想想看,如果没有接口,你写一个 attack 函数,可能传进来一个没有 hp 属性的对象,那不就炸了?接口就是提前约定好:你要当敌人,就必须有 name、hp、damage 这三个属性。
模块化开发基础:import 与 export
小游戏项目一大了,所有代码写在一个文件里?那简直是灾难。模块化就是把你代码拆成一个个小文件,每个文件只负责一件事。TypeScript 沿用了 ES6 的模块语法。
// ----- player.ts -----
export interface IPlayer {
name: string;
hp: number;
}
export class Player implements IPlayer {
constructor(public name: string, public hp: number) {}
}
// ----- enemy.ts -----
export interface IEnemy {
name: string;
hp: number;
}
// ----- main.ts -----
import { Player, IPlayer } from './player';
import { IEnemy } from './enemy';
const hero: IPlayer = new Player('英雄', 100);
const monster: IEnemy = { name: '史莱姆', hp: 30 };
console.log(`${hero.name} 遇到了 ${monster.name}`);
我个人习惯,每个类或每个功能模块单独一个文件。比如 player.ts、enemy.ts、skill.ts、ui.ts。这样找代码特别快,而且多人协作时不容易冲突。
export default 可以导出一个默认值,但我不太推荐。因为 export default 在导入时可以随便起名字,容易造成命名混乱。我更喜欢用命名导出(export { ... }),这样导入时名字是确定的,IDE 的自动补全也更友好。
总结一下
TypeScript 入门其实就这几板斧:
- 变量声明:多用
const,少用let,不用var。类型注解写清楚。 - 函数:参数和返回值都加类型,可选参数用问号。
- 类与接口:接口定义契约,类实现逻辑。私有属性用
private保护起来。 - 模块化:一个文件一个功能,用
import/export串联起来。
这些基础打牢了,后面写小游戏逻辑时,你会发现自己写代码的速度反而更快了——因为类型系统帮你挡住了很多低级错误。下一章,我们会把这些知识用到实际的小游戏开发中,比如做一个简单的玩家移动和攻击系统。到时候你就知道,今天学的这些,全是干货。