1. TypeScript 概述

TypeScript 是 JavaScript 的超集,在 JavaScript 的基础上增加了静态类型系统。
它并不会取代 JavaScript,而是在代码编译阶段帮助开发者提前发现一部分类型错误,最后仍然会被编译成 JavaScript 运行。

1.1 TypeScript 的优点

  • 编译阶段可以发现部分类型错误;
  • IDE 的代码提示、跳转和重构能力更强;
  • 函数参数、返回值和数据结构更清晰;
  • 大型项目和多人协作时更容易维护;
  • 修改接口或重构代码时更加安全。

1.2 TypeScript 的缺点

  • 需要额外学习类型系统;
  • 类型写得过于复杂时,可读性可能下降;
  • 第三方库缺少类型声明时,需要自己补充 .d.ts 文件;
  • 如果项目中大量使用 any,类型系统会逐渐失去意义。

1.3 适用场景

TypeScript 比较适合:
  • 大型项目;
  • 多人协作项目;
  • 接口和数据结构较复杂的项目;
  • 前端工程化项目;
  • Cocos Creator 游戏客户端项目;
  • 需要长期维护和频繁重构的项目。
在 Cocos Creator 项目中,TypeScript 可以明确描述 NodeButtonLabel、资源对象、商品数据、关卡配置等类型,减少组件获取和数据传递时的错误。

2. TypeScript 基础类型

TypeScript 中常见的基础类型包括:
JavaScript 原有的基础值类型包括:
  • string
  • number
  • boolean
  • null
  • undefined
  • symbol
  • bigint
TypeScript 在此基础上又提供了:
  • 数组;
  • 元组;
  • 枚举;
  • 对象类型;
  • 联合类型;
  • 交叉类型;
  • any
  • unknown
  • void
  • never

3. 数组与元组

本章从「Array 数组」和「Tuple 元组」两方面说明数组与元组。

3.1 Array 数组

数组表示一组数量通常不固定、类型相同或结构相近的数据。
数组也可以使用联合类型:
所以“数组中的元素只能有一种类型”并不准确,更准确的说法是:数组中的元素必须符合数组声明的元素类型。

3.2 Tuple 元组

元组用于表示固定长度、固定位置类型的数据。
其中:
  • 第 0 位必须是 string
  • 第 1 位必须是 number
常见使用场景:
注意:数组适合表示一组同类数据,元组适合表示固定结构的数据。

4. 枚举 enum

枚举用于定义一组有限的命名常量。
字符串枚举:
常见使用场景:
  • 游戏状态;
  • 角色状态;
  • 网络消息类型;
  • 事件类型;
  • UI 面板类型;
  • 道具类型;
  • 动画状态。
注意:enum 编译后通常会生成运行时代码。
如果只需要类型约束,也可以使用联合字面量:

5. keyof、typeof 与 as const

本章从「keyof」「typeof」和「keyof typeof」等方面说明 keyof、typeof 与 as const。

5.1 keyof

keyof 用于获取一个对象类型中所有键组成的联合类型。
常见写法:

5.2 typeof

typeof 在 JavaScript 和 TypeScript 中有两个不同语境。
5.2.1 JavaScript 运行时
5.2.2 TypeScript 类型语境
此时 User 等价于:
简单理解:
  • keyof:这个类型有哪些字段;
  • typeof:这个变量是什么类型。

5.3 keyof typeof

执行顺序:
  1. typeof GoodsType 获取对象类型;
  1. keyof 再获取该类型所有键。

5.4 as const

as const 会让 TypeScript 使用更精确的字面量类型,而不是把数据宽化成普通的 string[]

6. any、unknown、void 与 never

本章从「any」「unknown」和「void」等方面说明 any、unknown、void 与 never。

6.1 any

any 表示任意类型,会关闭大部分类型检查。
上面的代码编译时可能不报错,但运行时可能出现异常。
注意:大量使用 any 会让 TypeScript 逐渐失去意义。

6.2 unknown

unknown 同样表示未知类型,但比 any 更安全。
在使用 unknown 之前,必须先进行类型判断、类型收窄或断言。

6.3 void

void 通常表示函数没有需要使用的返回值。

6.4 never

never 表示永远不会正常产生的值。
也可以用于穷尽检查:

7. 联合类型与交叉类型

本章从「联合类型」和「交叉类型」两方面说明联合类型与交叉类型。

7.1 联合类型

联合类型使用 |,表示一个值可以是多种类型中的一种。
对象联合通常会和可辨识联合一起使用:

7.2 交叉类型

交叉类型使用 &,表示同时拥有多个类型的全部成员。
简单理解:
  • 联合类型:或;
  • 交叉类型:且。

8. type 与 interface

typeinterface 都可以描述对象结构。

8.1 共同点

  • 都可以描述对象结构;
  • 都可以被类实现;
  • 都可以扩展已有类型。

8.2 interface 的特点

interface 支持声明合并:
最终会合并为同一个 Window 类型。

8.3 type 的特点

type 可以描述更多复杂类型:
常见选择方法:
  • 描述对象结构、公共接口时优先使用 interface
  • 联合类型、交叉类型、元组和复杂类型运算使用 type

9. 泛型

泛型可以理解为“类型参数”。
它允许函数、类和接口在定义时不写死具体类型,在使用时再确定类型。

9.1 函数泛型

很多情况下 TypeScript 可以自动推导泛型参数:

9.2 接口泛型

9.3 类泛型

泛型的作用:
  • 提高代码复用性;
  • 保留明确类型;
  • 减少 any
  • 让通用数据结构和工具函数更加安全。
在游戏项目中,资源加载、对象池、接口返回结果和列表组件都适合使用泛型。

10. TypeScript 工具类型

TypeScript 内置了许多常用工具类型。

10.1 Partial

将所有属性变成可选属性:
适合局部更新:

10.2 Required

将所有属性变成必填属性:

10.3 Readonly

将所有属性变成只读属性:

10.4 Pick

选取部分字段:

10.5 Omit

排除部分字段:

10.6 Record

创建键值映射:

10.7 ReturnType 与 Parameters

常见使用总结:
  • Partial:局部修改;
  • Pick:选择字段;
  • Omit:排除字段;
  • Record:字典映射;
  • ReturnType:获取函数返回值类型;
  • Parameters:获取函数参数元组。

11. 类与访问修饰符

TypeScript 支持 publicprotectedprivate
修饰符
当前类
子类
类外部
public
可以
可以
可以
protected
可以
可以
不可以
private
可以
不可以
不可以
public 是默认修饰符。

11.1 private# 私有字段

区别:
  • private 主要由 TypeScript 在编译阶段检查;
  • #salary 是 JavaScript 原生私有字段;
  • # 私有字段在运行时也不能从类外部直接访问。

12. 抽象类

抽象类使用 abstract class 声明,不能被直接实例化,只能被继承。
抽象类可以:
  • 提供公共属性;
  • 提供已经实现的方法;
  • 定义必须由子类实现的抽象方法;
  • 作为一组子类的公共基类。
抽象类和接口的主要区别:
  • 抽象类会生成运行时代码;
  • 接口只用于类型检查;
  • 一个类只能继承一个抽象类;
  • 一个类可以实现多个接口。

13. 常见符号

本章说明可选属性、可选链与空值合并等常见符号的含义和使用边界。

13.1 ? 可选

13.2 ?. 可选链

左侧为 nullundefined 时,不再继续访问,结果为 undefined
Cocos 中常见写法:

13.3 ?? 空值合并

只有左侧是 nullundefined 时才使用右侧默认值。

13.4 ! 非空断言

表示开发者确定结果不为空。
注意:非空断言只关闭编译检查,不会自动保证运行时一定有值。

13.5 ! 属性确定赋值断言

表示该属性会在其他位置完成初始化。

13.6 _ 下划线

数字分隔符:
表示暂时不使用的参数:

13.7 &|

  • &:交叉类型;
  • |:联合类型。

13.8 #

JavaScript 原生私有字段:

14. 模块导入与路径解析

示例:
假设当前文件是:
解析过程:
具体匹配到 .ts.tsx.d.ts.jspackage.json 导出项还是其他文件,取决于:
  • moduleResolution 配置;
  • 使用的运行时;
  • 打包器;
  • package.jsonexportsimports
  • TypeScript 版本和其他编译选项。
因此,“不以 ./../ 开头就一定从 node_modules 查找”过于简化。它属于裸模块说明符(bare specifier),如何解析取决于当前模块解析模式。

14.1 常见路径形式

14.2 路径别名

@/ 并不是 JavaScript 或 TypeScript 内置语法,需要在 tsconfig.json 中配置,并确保运行时或打包器也理解相同映射。
只配置 TypeScript 可能只让编辑器和类型检查通过;实际运行时仍需由 Vite、Webpack、Node.js 或对应框架配置解析。

14.3 是否写扩展名

在很多 TypeScript 项目中,相对导入会省略扩展名:
是否允许直接写 .ts,取决于编译配置和运行方式。不要把“TypeScript 总是不允许扩展名”或“总是允许扩展名”当成固定规则。

14.4 导入方式

import type 会明确表示只在类型检查阶段使用,避免无意生成运行时导入。

15. 扩展全局属性与第三方模块声明

本章从「扩展 window」和「声明第三方模块」两方面说明扩展全局属性与第三方模块声明。

15.1 扩展 window

可以创建 global.d.ts

15.2 声明第三方模块

第三方库没有类型声明时,可以自己补充:
全局变量:
.d.ts 文件只负责提供类型声明,不会自动实现真实功能。

16. Cocos Creator 中的 TypeScript 实践

在 Cocos Creator 项目中,TypeScript 常用于:
  • UI 组件;
  • 节点和组件绑定;
  • 游戏状态管理;
  • 资源加载;
  • 对象池;
  • 订单和商品数据;
  • 关卡配置;
  • 异步流程。

16.1 编辑器属性绑定

明确的组件类型可以让 IDE 提供正确提示,也能减少把 NodeButtonLabel 混用的问题。

16.2 数据结构

16.3 异步资源加载

多个资源并行加载:

16.4 实际使用中的问题

项目赶进度时容易出现:
这种写法虽然方便,但会绕过类型检查。
更好的方式是:
  • 给组件声明明确类型;
  • 使用接口描述数据结构;
  • 使用泛型封装资源加载;
  • 对外部数据先使用 unknown,再进行类型判断;
  • 尽量减少强制断言和 any

17. 常见易错点

  • unknown 不能直接使用,必须先进行类型收窄;
  • any 会关闭大部分类型检查;
  • voidnever 含义不同;
  • 数组可以使用联合元素类型,不是只能存一种类型;
  • 元组强调固定长度和固定位置类型;
  • keyof 获取类型的键,typeof 获取变量的类型;
  • enum 通常会产生运行时代码;
  • interface 支持声明合并;
  • type 更适合联合、交叉、元组和复杂类型运算;
  • private 主要是 TypeScript 编译期限制;
  • # 是 JavaScript 原生运行时私有字段;
  • ?. 只处理 nullundefined
  • ?? 不会把 0、空字符串和 false 当成空值;
  • 非空断言 ! 不能消除真实的运行时空引用;
  • .d.ts 只描述类型,不会实现第三方 SDK。

18. 总结

TypeScript 的价值在于把许多接口约束提前到编译阶段。实际项目中应从清晰的数据模型和函数边界开始,再使用泛型、联合类型、类型守卫和工具类型表达复用关系。
类型声明不能替代运行时校验;外部输入、网络数据和跨版本资源仍需要在运行时检查。

19. 参考资料