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 报错。加了类型注解后,编辑器直接标红,一秒定位问题。说白了,类型注解就是你的“代码说明书”。

避坑指南: 可选参数一定要放在必选参数后面。这是 TypeScript 的规则,也是 JavaScript 的惯例。我曾经因为顺序写反,导致调用时参数错位,排查了半天。

类与接口:面向对象的核心

小游戏开发中,类用得特别多。比如玩家、敌人、道具,都可以抽象成类。而接口,就是定义“这个类应该长什么样”的契约。

// 接口:定义结构
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 属性的对象,那不就炸了?接口就是提前约定好:你要当敌人,就必须有 namehpdamage 这三个属性。

重点: 接口只定义结构,不实现逻辑。类才负责具体实现。这是面向接口编程的核心思想——面向接口编程,而不是面向实现编程。这样后期换逻辑时,只要接口不变,调用方代码就不用改。

模块化开发基础: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.tsenemy.tsskill.tsui.ts。这样找代码特别快,而且多人协作时不容易冲突。

小技巧: 使用 export default 可以导出一个默认值,但我不太推荐。因为 export default 在导入时可以随便起名字,容易造成命名混乱。我更喜欢用命名导出(export { ... }),这样导入时名字是确定的,IDE 的自动补全也更友好。

总结一下

TypeScript 入门其实就这几板斧:

  • 变量声明:多用 const,少用 let,不用 var。类型注解写清楚。
  • 函数:参数和返回值都加类型,可选参数用问号。
  • 类与接口:接口定义契约,类实现逻辑。私有属性用 private 保护起来。
  • 模块化:一个文件一个功能,用 import/export 串联起来。

这些基础打牢了,后面写小游戏逻辑时,你会发现自己写代码的速度反而更快了——因为类型系统帮你挡住了很多低级错误。下一章,我们会把这些知识用到实际的小游戏开发中,比如做一个简单的玩家移动和攻击系统。到时候你就知道,今天学的这些,全是干货。