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 可以明确描述Node、Button、Label、资源对象、商品数据、关卡配置等类型,减少组件获取和数据传递时的错误。
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
执行顺序:
typeof GoodsType获取对象类型;
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
type 和 interface 都可以描述对象结构。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 支持
public、protected 和 private。修饰符 | 当前类 | 子类 | 类外部 |
public | 可以 | 可以 | 可以 |
protected | 可以 | 可以 | 不可以 |
private | 可以 | 不可以 | 不可以 |
public 是默认修饰符。11.1 private 与 # 私有字段
区别:
private主要由 TypeScript 在编译阶段检查;
#salary是 JavaScript 原生私有字段;
#私有字段在运行时也不能从类外部直接访问。
12. 抽象类
抽象类使用
abstract class 声明,不能被直接实例化,只能被继承。抽象类可以:
- 提供公共属性;
- 提供已经实现的方法;
- 定义必须由子类实现的抽象方法;
- 作为一组子类的公共基类。
抽象类和接口的主要区别:
- 抽象类会生成运行时代码;
- 接口只用于类型检查;
- 一个类只能继承一个抽象类;
- 一个类可以实现多个接口。
13. 常见符号
本章说明可选属性、可选链与空值合并等常见符号的含义和使用边界。
13.1 ? 可选
13.2 ?. 可选链
左侧为
null 或 undefined 时,不再继续访问,结果为 undefined。Cocos 中常见写法:
13.3 ?? 空值合并
只有左侧是
null 或 undefined 时才使用右侧默认值。13.4 ! 非空断言
表示开发者确定结果不为空。
注意:非空断言只关闭编译检查,不会自动保证运行时一定有值。13.5 ! 属性确定赋值断言
表示该属性会在其他位置完成初始化。
13.6 _ 下划线
数字分隔符:
表示暂时不使用的参数:
13.7 & 与 |
&:交叉类型;
|:联合类型。
13.8 #
JavaScript 原生私有字段:
14. 模块导入与路径解析
示例:
假设当前文件是:
解析过程:
具体匹配到
.ts、.tsx、.d.ts、.js、package.json 导出项还是其他文件,取决于:moduleResolution配置;
- 使用的运行时;
- 打包器;
package.json的exports和imports;
- 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 提供正确提示,也能减少把
Node、Button 和 Label 混用的问题。16.2 数据结构
16.3 异步资源加载
多个资源并行加载:
16.4 实际使用中的问题
项目赶进度时容易出现:
这种写法虽然方便,但会绕过类型检查。
更好的方式是:
- 给组件声明明确类型;
- 使用接口描述数据结构;
- 使用泛型封装资源加载;
- 对外部数据先使用
unknown,再进行类型判断;
- 尽量减少强制断言和
any。
17. 常见易错点
unknown不能直接使用,必须先进行类型收窄;
any会关闭大部分类型检查;
void和never含义不同;
- 数组可以使用联合元素类型,不是只能存一种类型;
- 元组强调固定长度和固定位置类型;
keyof获取类型的键,typeof获取变量的类型;
enum通常会产生运行时代码;
interface支持声明合并;
type更适合联合、交叉、元组和复杂类型运算;
private主要是 TypeScript 编译期限制;
#是 JavaScript 原生运行时私有字段;
?.只处理null和undefined;
??不会把0、空字符串和false当成空值;
- 非空断言
!不能消除真实的运行时空引用;
.d.ts只描述类型,不会实现第三方 SDK。
18. 总结
TypeScript 的价值在于把许多接口约束提前到编译阶段。实际项目中应从清晰的数据模型和函数边界开始,再使用泛型、联合类型、类型守卫和工具类型表达复用关系。
类型声明不能替代运行时校验;外部输入、网络数据和跨版本资源仍需要在运行时检查。