TypeScript 基础类型与变量声明
📚 TypeScript 教程系列
⚠️ 来源声明:本文内容参考自 菜鸟教程 TypeScript 教程,仅供学习交流,版权归原作者所有。
TypeScript 的核心价值在于静态类型系统,它让 JavaScript 在编译阶段就能捕获大量潜在错误。本章将系统梳理 TypeScript 的基础类型体系、变量声明方式,以及 null/undefined、Symbol、Number、String 等原始类型的深入用法,并讲解类型推断与类型断言这两项让类型系统既灵活又可控的关键机制。
基础类型
基础类型可以让开发者更准确地描述数据的结构和意图。TypeScript 包含的数据类型如下表所示:
| 类型 | 描述 | 示例 |
|---|---|---|
string | 表示文本数据 | let name: string = "Alice"; |
number | 表示数字,包括整数和浮点数 | let age: number = 30; |
boolean | 表示布尔值 true 或 false | let isDone: boolean = true; |
array | 表示相同类型的元素数组 | let list: number[] = [1, 2, 3]; |
tuple | 表示已知类型和长度的数组 | let person: [string, number] = ["Alice", 30]; |
enum | 定义一组命名常量 | enum Color { Red, Green, Blue }; |
any | 任意类型,不进行类型检查 | let value: any = 42; |
void | 无返回值(常用于函数) | function log(): void {} |
null | 表示空值 | let empty: null = null; |
undefined | 表示未定义 | let undef: undefined = undefined; |
never | 表示不会有返回值 | function error(): never { throw new Error("error"); } |
object | 表示非原始类型 | let obj: object = { name: "Alice" }; |
union | 联合类型,表示可以是多种类型之一 | let id: string | number; |
unknown | 不确定类型,需类型检查后再使用 | let value: unknown = "Hello"; |
需要注意的是,TypeScript 和 JavaScript 都没有独立的整数类型,所有数字统一用 number 表示。
类型如何映射到 JavaScript
TypeScript 的类型信息只存在于编译期:编译为 JavaScript 后,几乎所有类型注解都会被完全删除,运行时只剩下普通的 JS 值。这意味着类型检查无法在运行时生效,类型错误只能在编译阶段被发现。各类型编译后的去向如下:
| 类型 | 编译后 | 运行时表现 |
|---|---|---|
string、number、boolean | 删除类型注解 | 普通 JS 字符串 / 数字 / 布尔值(number 无整数类型,均为双精度浮点数) |
array、tuple | 删除类型注解 | 普通 JS 数组,元素类型与长度约束完全失效 |
object | 删除类型注解 | 普通 JS 对象 |
null、undefined | 删除类型约束 | JS 原生值本身,strictNullChecks 仅编译期生效 |
any、unknown | 删除类型注解 | 无类型的 JS 值;any 绕过检查,unknown 的检查仅编译期有效 |
void、never | 删除类型注解 | void 函数正常返回 undefined;never 函数正常执行(抛异常或死循环) |
| 联合类型、字面量类型 | 删除类型注解 | 普通 JS 值,类型信息完全消失 |
symbol | 删除类型注解 | Symbol() 创建的唯一值 |
enum | 生成运行时代码 | 编译为 IIFE 生成双向映射对象(Color.Red === 0 且 Color[0] === "Red");const enum 则内联为常量,不生成对象 |
Number、String(包装对象) | 删除类型注解 | new Number() / new String() 创建的对象实例,typeof 为 "object" |
| 类型推断、类型断言 | 纯编译期机制 | 不产生任何运行时代码,断言不改变运行时类型 |
核心原则: 除
enum会生成运行时对象外,TypeScript 的所有类型——注解、推断、断言——都是编译期的"幻影",编译后即消失,运行时只剩 JavaScript 本身。正因如此,类型断言并非类型转换:"42" as number在运行时仍是字符串,真正的转换需借助Number()、String()等函数。
string 字符串
string 表示文本数据,只能存储字符串,通常用于描述文字信息。
1 | |
TypeScript 支持模板字符串,用反引号 `(注意不是单引号 ')来定义,允许在字符串中插入变量或表达式,非常适合多行文本和拼接变量。
1 | |
字符串字面量也支持多行书写与内嵌表达式:
1 | |
number 数字
TypeScript 使用 number 表示所有数字,包括整数、浮点数,以及二进制、八进制、十六进制字面量。
1 | |
boolean 布尔值
boolean 表示逻辑值 true 或 false,用于条件判断。
1 | |
array 数组
数组可以表示一组相同类型的元素,有两种声明方式:type[] 或 Array<type>(数组泛型)。
1 | |
tuple 元组
元组表示已知数量和类型的数组,每个元素可以是不同的类型,适合表示固定结构的数据。
1 | |
enum 枚举
enum 用来定义一组命名常量,是对 JavaScript 标准数据类型的一个补充。默认情况下枚举的值从 0 开始递增。
1 | |
也可以手动指定成员的数值,未显式赋值的成员会接着前一个的值递增:
1 | |
枚举类型提供的一个便利是可以由枚举的值反查它的名字:
1 | |
如果一个枚举成员的值是被计算出来的(例如调用函数),那么它后面一位的成员必须显式初始化值,否则编译不通过:
1 | |
any 类型
any 可以表示任何类型,适合不确定数据类型的情况。但使用时需谨慎,因为 any 会绕过类型检查。
1 | |
任意值是 TypeScript 针对编程时类型不明确的变量使用的一种数据类型,常用于以下三种情况。
1. 变量的值会动态改变时,比如来自用户的输入,任意值类型可以让这些变量跳过编译阶段的类型检查:
1 | |
2. 改写现有代码时,任意值允许在编译时可选择地包含或移除类型检查:
1 | |
3. 定义存储各种类型数据的数组时:
1 | |
void 空类型
void 用于没有返回值的函数。声明变量时,类型 void 意味着只能赋值 null 或 undefined。
1 | |
null 和 undefined
null 和 undefined 分别表示"空值"和"未定义"。在 JavaScript 中:
null表示"什么都没有",是一个只有一个值的特殊类型,表示一个空对象引用,用typeof检测null返回的是object。undefined表示一个没有设置值的变量,typeof一个没有值的变量会返回undefined。
在默认情况下,null 和 undefined 是所有类型(包括 void)的子类型,可以赋值给其它类型。而启用 --strictNullChecks 严格空校验后,它们只能被赋值给 void 或本身对应的类型:
1 | |
如果一个类型可能出现 null 或 undefined,可以用 | 联合类型来支持多种类型:
1 | |
1 | |
never 类型
never 表示不会有返回值,通常用于抛出错误或进入无限循环的函数,表示该函数永远不会正常结束。never 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。
1 | |
声明为 never 类型的变量只能被 never 类型所赋值。在函数中它通常表现为抛出异常或无法执行到终止点:
1 | |
object 对象类型
object 表示非原始类型的值,适用于复杂的对象结构。
1 | |
联合类型
联合类型表示一个变量可以是多种类型之一,通过 | 符号实现。
1 | |
unknown 不确定类型
unknown 与 any 类似,但更严格。必须经过类型检查后才能赋值给其他类型变量。
1 | |
字面量类型
字面量类型可以让变量只能拥有特定的值,常结合联合类型定义变量的特定状态。
1 | |
综合实例
以下实例展示了 TypeScript 中主要基础类型的定义和使用,模拟一个用户对象和相关的操作函数:
1 | |
说明:
- 枚举类型 (
Role):用于定义用户角色的命名常量Admin、User和Guest。 - 接口 (
User):定义了User对象的结构,展示了string、number、boolean、array和tuple类型的使用。 - 函数
getUserInfo:返回用户的描述信息,使用了字符串插值并结合enum。 - 函数
printUserInfo:类型为void,因为它仅输出信息,不返回任何值。 - 联合类型 (
number | string):用于findUser函数的参数,可以接受数字或字符串。 never类型:throwError函数抛出错误,不会正常返回。any类型:展示如何声明一个可以接收任何类型的变量。unknown类型:类似any,但更加安全,示例中在类型断言之前进行类型检查。
变量声明
变量是一种使用方便的占位符,用于引用计算机内存地址,就像存放数据的容器。TypeScript 遵循强类型语法,声明变量时需要指定类型。
变量命名规则
- 名称可以包含数字和字母。
- 除了下划线
_和美元$符号外,不能包含其他特殊字符,包括空格。 - 名称不能以数字开头。
四种声明方式
TypeScript 变量声明主要有以下四种形式:
1. 声明变量的类型及初始值:
1 | |
例如:var uname:string = "Runoob";
2. 声明变量的类型,但没有初始值,变量值会设置为 undefined:
1 | |
例如:var uname:string;
3. 声明变量并初始值,但不设置类型,该变量可以是任意类型:
1 | |
例如:var uname = "Runoob";
4. 声明变量既没有类型,也没有初始值,该变量可以是任意类型,默认值为 undefined:
1 | |
例如:var uname;
完整示例
1 | |
编译后的 JavaScript:
1 | |
输出结果:
1 | |
注意: 避免使用
name作为变量名,因为它会与 DOM 中的全局window.name属性冲突。
TypeScript 遵循强类型语法,类型不匹配会导致编译错误:
1 | |
let 与 const
除了 var,TypeScript 还支持 let 和 const:
var:函数作用域,存在变量提升,容易导致意外行为。let:块级作用域,不存在变量提升,同一作用域内不能重复声明。const:块级作用域,声明时必须初始化,且不可重新赋值(但对象属性仍可修改)。
1 | |
解构声明
TypeScript 支持数组解构和对象解构,方便从数组或对象中提取值:
1 | |
变量作用域
变量作用域决定了变量在哪里被定义和访问。TypeScript 有三种作用域:
- 全局作用域:全局变量定义在程序结构之外,可以在代码的任何地方访问。
- 类作用域:也称为字段,在类内部声明但在方法外部。可以通过类的对象访问。静态变量通过类名直接访问。
- 局部作用域:局部变量只能在声明它们的代码块(如方法)内部使用。
1 | |
编译后的 JavaScript:
1 | |
输出结果:
1 | |
在方法外访问 local_num 会报错:error TS2322: Could not find symbol 'local_num'.
特殊类型
TypeScript 的类型系统除了常规类型外,还提供了 never、void、unknown、any 四种特殊类型处理特定场景。理解它们的区别有助于编写更安全、更准确的代码。
四种特殊类型对比
| 类型 | 描述 | 特点 |
|---|---|---|
| any | 任意类型 | 绕过类型检查,无安全性 |
| unknown | 安全任意类型 | 使用需检查,有安全性 |
| void | 无返回值 | 用于函数,可返回 undefined |
| never | 永不返回 | 抛出异常/循环,所有类型的子类型 |
类型安全等级从低到高为:any(最低)→ unknown → void → never(最高,最严格)。
never 类型
never 表示永不出现的值,用于函数抛出异常或运行无限循环。never 是所有类型的子类型,这意味着 never 可以赋值给任何类型,但任何类型都不能赋值给 never(除了 never 本身)。
1 | |
运行结果:never 赋值给 number: undefined
void 类型
void 表示没有返回值,用于没有 return 语句的函数。void 实际上和 undefined 很相似,主要用于函数的返回类型声明。
1 | |
运行结果:
1 | |
unknown 类型
unknown 是 any 的类型安全版本。使用前必须进行类型检查,这保护了类型安全。
1 | |
运行结果:字符串长度: 5
any 类型
any 完全绕过类型检查,可以被赋值为任何类型,也可以赋值给任何类型。应该尽量避免使用。
1 | |
any vs unknown 对比
| 特性 | any | unknown |
|---|---|---|
| 接受任意类型 | 是 | 是 |
| 直接赋值给其他类型 | 是 | 否(需检查) |
| 直接调用方法 | 是 | 否(需检查) |
| 类型安全 | 否(无) | 是(有) |
建议: 处理未知类型的数据时,优先使用
unknown而不是any,这样能强制进行类型检查。
never vs void 对比
| 特性 | never | void |
|---|---|---|
| 含义 | 永不返回 | 没有返回值 |
| 用于函数 | 抛出异常/无限循环 | 普通无返回值函数 |
| 可赋值给其他类型 | 是(子类型) | 只能赋值给 void/any |
never 表示函数永远不会正常返回(要么抛异常,要么死循环);void 表示函数正常执行完毕,但没有返回值。
实际应用:穷举检查
never 类型常用于穷举检查(Exhaustive Check),确保所有分支都被处理。如果新增了一个分支却没有更新 switch,编译器就会报错。
1 | |
运行结果:
1 | |
特殊类型注意事项
never可以赋值给任何类型,但任何类型都不能赋值给never。- 在 JavaScript 中,
void和undefined本质上等价。 unknown类型的值在使用前必须检查。- 尽量避免
any,优先使用unknown。
编写代码时,优先使用更严格的类型。处理未知数据使用 unknown,函数无返回值使用 void,需要穷举检查时使用 never。
null 和 undefined
在 JavaScript 中,null 和 undefined 是常见的错误来源,很多运行时错误都与之相关。TypeScript 的 strictNullChecks 选项要求开发者显式处理可能为空的值,这虽然增加了编码工作量,但能大幅减少空值导致的运行时错误,提高代码可靠性。
null 表示"空值",即这里没有值;undefined 表示"未定义",即这里还没有被赋值。
null 和 undefined 基础
null 和 undefined 在 TypeScript 中是独立的类型。不启用 strictNullChecks 时,它们可以相互赋值;启用后需要显式声明。
1 | |
运行结果:
1 | |
联合类型处理 null
启用 strictNullChecks 后,需要使用联合类型显式声明可能为 null 的值,并通过类型守卫收窄类型。
1 | |
运行结果:
1 | |
最佳实践: 使用类型守卫(
if检查)来收窄类型,让 TypeScript 知道具体是什么类型。
可选参数和属性
可选参数和可选属性自动包含 undefined,不需要显式声明 null。可选参数 name?: string 等同于 string | undefined。
1 | |
运行结果:
1 | |
非空断言运算符
使用 ! 告诉编译器值不为 null 或 undefined。这应该谨慎使用,因为它会绕过类型检查,如果值实际为 null,运行时仍会报错。
1 | |
空值合并运算符
使用 ?? 提供默认值,只有当值为 null 或 undefined 时才使用默认值。优先使用 ?? 而不是 ||,因为 ?? 只会处理 null 和 undefined,不会错误地将 0 或空字符串视为 falsy。
1 | |
运行结果:
1 | |
可选链
使用 ?. 安全访问可能不存在的嵌套属性,避免多层嵌套时的空值检查。可选链是处理嵌套可选属性的最佳方式,配合 ?? 可以提供默认值的兜底。
1 | |
运行结果:
1 | |
null/undefined 处理总结
- 严格模式: 建议启用
strictNullChecks以获得更好的类型安全。 - 联合类型: 使用
string | null声明可能为空的值。 - 可选参数/属性: 自动包含
undefined。 - 非空断言: 使用
!(谨慎使用,尽量避免)。 - 空值合并: 使用
??提供默认值。 - 可选链: 使用
?.安全访问。
使用类型守卫、可选链和空值合并运算符来处理 null 和 undefined,让代码更安全、更易读。
Symbol
Symbol 是 ES6 引入的原始数据类型,表示唯一的标识符。在 JavaScript 中,对象的属性名都是字符串,有时可能会发生冲突。Symbol 提供了创建唯一标识符的方式,每次调用 Symbol() 都会创建一个新的、唯一的值,即使描述相同也不相等。这在需要创建私有属性、定义唯一常量、实现迭代器等场景非常有用。
创建 Symbol
使用 Symbol() 函数创建唯一的 Symbol 值,可以传入一个可选的描述字符串。
1 | |
运行结果:
1 | |
这是 Symbol 最重要的特性:每次调用 Symbol() 都会创建一个新值,与任何其他值都不相等。
Symbol 作为对象属性
Symbol 可以用作对象的属性键,创建唯一的属性名。Symbol 属性不会出现在 JSON 序列化中,也不会被 for...in 遍历到,可以用来创建"私有"属性。
1 | |
运行结果:
1 | |
全局 Symbol 注册表
使用 Symbol.for() 访问全局注册表中的 Symbol,相同 key 会返回相同的 Symbol。这与 Symbol() 不同:Symbol() 每次创建新的值,Symbol.for() 在全局注册表中查找或创建。
1 | |
运行结果:
1 | |
内置 Symbol
JavaScript 定义了一些内置的 Symbol 值,用于自定义语言行为,是 JavaScript 高级特性的基础。
1 | |
运行结果:
1 | |
常见的内置 Symbol 包括:
Symbol.iterator:定义对象的默认迭代器。Symbol.toStringTag:自定义对象的toString()返回值。Symbol.hasInstance:自定义instanceof的行为。
Symbol 类型注解
在 TypeScript 中使用 symbol 类型(小写)进行类型注解。
1 | |
Symbol 注意事项
- 唯一性: 每次
Symbol()调用的值都不同。 - 不可枚举: Symbol 属性不会出现在
for...in循环中。 - JSON 忽略: Symbol 属性不会被 JSON 序列化。
- 全局注册:
Symbol.for()在全局注册表中共享。
在需要创建唯一标识符、避免属性名冲突、或需要"私有"属性时,使用 Symbol。
Number
TypeScript 与 JavaScript 一样支持 Number 对象。Number 对象用于包装数值类型,是引用类型,与基本的 number 类型不同。虽然 Number 对象提供了额外的属性和方法,但在 TypeScript 中更推荐直接使用基本的 number 类型。
语法
1 | |
这会创建一个引用类型的对象,而不是原始的 number 类型。如果参数无法转换为数字,则返回 NaN。
Number 对象与 number 类型的区别
- 基本类型
number:原始数据类型,用于存储数值。 Number对象:引用类型,是一个包装对象,用于包装基本数值。
1 | |
Number 对象属性
| 序号 | 属性 & 描述 |
|---|---|
| 1 | MAX_VALUE — 可表示的最大的数,接近 1.79E+308。大于此值为 Infinity。 |
| 2 | MIN_VALUE — 最接近 0 的正数,约 5e-324。小于此值转换为 0。 |
| 3 | NaN — 非数字值(Not-A-Number)。 |
| 4 | NEGATIVE_INFINITY — 负无穷大,溢出时返回。小于 MIN_VALUE。 |
| 5 | POSITIVE_INFINITY — 正无穷大,溢出时返回。大于 MAX_VALUE。 |
| 6 | prototype — 静态属性,允许向对象添加属性和方法。 |
| 7 | constructor — 返回创建此对象的 Number 函数引用。 |
1 | |
输出结果:
1 | |
NaN 实例
1 | |
输出:月份是:NaN
prototype 实例
1 | |
输出结果:
1 | |
Number 对象方法
| 序号 | 方法 & 描述 | 实例 |
|---|---|---|
| 1 | toExponential() — 把对象的值转换为指数计数法。 | var num1 = 1225.30; var val = num1.toExponential(); console.log(val); // 1.2253e+3 |
| 2 | toFixed() — 把数字转换为字符串,并对小数点指定位数。 | var num3 = 177.234; console.log(num3.toFixed()); // 177; console.log(num3.toFixed(2)); // 177.23; console.log(num3.toFixed(6)); // 177.234000 |
| 3 | toLocaleString() — 把数字转换为字符串,使用本地数字格式顺序。 | var num = new Number(177.1234); console.log(num.toLocaleString()); // 177.1234 |
| 4 | toPrecision() — 把数字格式化为指定的长度。 | var num = new Number(7.123456); console.log(num.toPrecision()); // 7.123456; console.log(num.toPrecision(1)); // 7; console.log(num.toPrecision(2)); // 7.1 |
| 5 | toString() — 把数字转换为字符串,使用指定的基数(2~36),省略则用基数 10。 | var num = new Number(10); console.log(num.toString()); // 10; console.log(num.toString(2)); // 1010; console.log(num.toString(8)); // 12 |
| 6 | valueOf() — 返回一个 Number 对象的原始数字值。 | var num = new Number(10); console.log(num.valueOf()); // 10 |
注意:
toFixed()和toPrecision()返回的是string类型,而不是number类型。
1
2
3var a = 177.234;
var b = a.toFixed();
console.log(b, typeof b); // 177 string
Number 使用建议
在 TypeScript 中更推荐直接使用基本的 number 类型,而不是 Number 对象,原因有三:
- 性能:基本类型更轻量,性能更好。
- 类型一致性:TypeScript 的类型系统更倾向于基本类型,使用
Number对象可能导致意外的类型不匹配。 - 最佳实践:基本类型的
number更符合 TypeScript 的最佳实践,避免了对象包装带来的不必要复杂性。
如果确实需要 Number 对象的方法,可以使用 valueOf() 转换回原始值:
1 | |
String
在 TypeScript 中,string 类型表示文本数据。它继承并扩展了 JavaScript 的字符串能力,同时增加了静态类型检查。
创建字符串
有两种创建字符串的形式:
1 | |
方式一:字符串字面量(推荐)
这是最常见且性能最好的方式,创建的是原始的 string 类型,是 TypeScript 类型系统中默认的字符串类型。
1 | |
方式二:String 对象(不推荐)
使用 new String() 创建的是包装对象类型(String),它是一个对象而非原始值,会引入类型混淆和性能开销。
1 | |
string 与 String 的区别
字符串字面量和 String 对象在类型上不同:字面量是原始的 string 数据类型,而 String 对象是引用类型。
1 | |
| 特性 | 字符串字面量 (string) | String 对象 (String) |
|---|---|---|
| 类型本质 | 原始值 | 引用类型(对象) |
| 性能 | 高效,无额外开销 | 低效,创建对象实例 |
| 类型检查 | 符合 TS 基本类型 | 类型不匹配(string 变量不能接受 String 对象) |
| 比较 | 直接值比较 | 引用比较(需用 .valueOf() 获取原始值) |
在 TypeScript 中,string 字面量类型和 String 对象类型并不完全兼容:
1 | |
String 对象属性
| 序号 | 属性 & 描述 |
|---|---|
| 1 | constructor — 对创建对象的函数的引用。var str = new String("This is string"); console.log("str.constructor is:" + str.constructor); 输出 str.constructor is:function String() { [native code] } |
| 2 | length — 返回字符串的长度。var uname = new String("Hello World"); console.log("Length " + uname.length); 输出 11 |
| 3 | prototype — 允许向对象添加属性和方法。 |
String 方法
| 序号 | 方法 & 描述 |
|---|---|
| 1 | charAt() — 返回指定位置的字符。var str = new String("RUNOOB"); console.log(str.charAt(0)); // R |
| 2 | charCodeAt() — 返回指定位置字符的 Unicode 编码。console.log(str.charCodeAt(0)); // 82 |
| 3 | concat() — 连接两个或多个字符串,返回新字符串。var str3 = str1.concat(str2); |
| 4 | indexOf() — 返回指定值首次出现的位置。str1.indexOf("OO"); // 3 |
| 5 | lastIndexOf() — 从后向前搜索,返回指定值最后出现的位置。 |
| 6 | localeCompare() — 用本地特定顺序比较两个字符串。 |
| 7 | match() — 找到一个或多个正则表达式的匹配。str.match(/ain/g); |
| 8 | replace() — 替换与正则表达式匹配的子串。str.replace(re, "$2, $1"); |
| 9 | search() — 检索与正则表达式匹配的值。 |
| 10 | slice() — 提取字符串的片断,返回新字符串。 |
| 11 | split() — 把字符串分割为子字符串数组。str.split(" ", 3); |
| 12 | substr() — 从起始索引号提取指定数目的字符。 |
| 13 | substring() — 提取两个指定索引之间的字符。str.substring(1, 2); // U |
| 14 | toLocaleLowerCase() — 根据主机 locale 转为小写。 |
| 15 | toLocaleUpperCase() — 根据主机 locale 转为大写。 |
| 16 | toLowerCase() — 转为小写。 |
| 17 | toString() — 返回字符串。 |
| 18 | toUpperCase() — 转为大写。 |
| 19 | valueOf() — 返回 String 对象的原始值。 |
方法示例
1 | |
String 使用建议
在 TypeScript 中,通常没有必要使用 String 对象。直接使用 string 字面量更高效,也更符合最佳实践:
- 性能:
String对象是引用类型,占用更多内存,每次创建实例开销更大。 - 类型安全:TypeScript 鼓励使用
string字面量类型,代码更简洁、更一致。
如果确实需要 String 对象的方法,可以先调用 .valueOf() 将对象转换为原始字符串:
1 | |
类型推断
类型推断是 TypeScript 最强大和便捷的特性之一。当没有明确指定类型时,TypeScript 编译器会分析代码上下文,自动确定变量类型——包括变量的初始值、函数的返回值等。类型推断让开发者可以在大多数情况下省略显式的类型声明,写出既简洁又类型安全的代码。
基础类型推断
当使用 var、let 或 const 声明变量并赋予初始值时,TypeScript 会根据初始值推断变量类型。
1 | |
一旦推断出类型,后续赋值必须匹配该类型,否则编译报错:
1 | |
错误信息:error TS2322: Type '"12"' is not assignable to type 'number'.
函数返回类型推断
TypeScript 会根据函数中的 return 语句推断返回类型。
1 | |
提示: 在实际项目中,建议显式声明函数的返回类型,提高代码可读性。
上下文类型推断
当变量出现在特定的上下文位置时,TypeScript 会根据使用位置反推其类型。
1 | |
输出:翻倍数组: 2,4,6,8,10
最佳通用类型推断
当数组的初始值包含多种类型时,TypeScript 会推断出联合类型。
1 | |
类型推断的限制
如果声明变量时没有初始值,TypeScript 会将其推断为 any,这会失去类型安全。
1 | |
警告: 避免使用未初始化的变量——应提供初始值或显式声明类型。
泛型函数推断
TypeScript 会根据函数调用时的参数自动推断泛型类型参数。
1 | |
类型推断注意事项
- 初始值的重要性: 始终为变量提供初始值以获得正确的类型推断。
- 显式声明: 当类型复杂或不明确时,显式声明类型。
- any 类型的风险: 未初始化的变量会成为
any,失去类型安全。 - strict 模式: 启用严格模式以避免
any的滥用。
善用类型推断可以减少冗余代码,但在关键位置仍应显式声明类型,写出既简洁又类型安全的代码。
类型断言
类型断言(Type Assertion)是一种告诉编译器"我比你更清楚这个值的类型"的机制。它赋予开发者手动覆盖 TypeScript 类型推断结果的能力,类似其他语言中的类型转换,但仅在编译阶段生效,不产生任何运行时代码。
两种语法形式
类型断言有两种写法:
1 | |
运行结果:
1 | |
在 React 的 .tsx 文件中,尖括号写法会与 JSX 标签冲突,必须使用 as 语法。推荐统一使用 as 语法。
TypeScript 判断断言是否有效的规则是:当类型 S 是类型 T 的子类型,或 T 是 S 的子类型时,S 能被成功断言成 T。这提供了额外的安全性——毫无根据的断言是危险的,如需要可以使用 any 作为中转。
类型断言不被称为类型转换,因为转换意味着运行时的支持,而类型断言纯粹是一个编译时语法,只用于指导编译器如何分析代码。
处理 any / unknown 类型
从外部接口、JSON 解析等场景获取的数据通常为 any,通过断言可以恢复类型提示。
1 | |
收窄联合类型
当已知联合类型中的具体分支,但编译器无法自动判断时,可使用断言明确类型。应优先使用类型守卫(typeof / instanceof / 判别属性)收窄类型,只在守卫无法覆盖的场景才使用断言。
1 | |
操作 DOM 元素
document.querySelector 返回的是 Element | null,断言为具体元素类型后才能访问其专属属性。
1 | |
非空断言 !
在值后加 !,告诉编译器该值不为 null 或 undefined,适用于你能确保非空但编译器无法推断的场景。非空断言会绕过编译器的空值检查,若实际值为 null / undefined,运行时仍会抛出错误。在条件允许时优先使用可选链 ?. 代替。
1 | |
常量断言 as const
as const 将字面量推断为最窄的只读字面量类型,常用于定义枚举值、配置常量和元组。
1 | |
断言不是类型转换
类型断言只影响编译器的类型检查,编译后不生成任何转换代码。将 "42" 断言为 number,运行时它仍然是字符串。真正的类型转换需要使用 Number()、String() 等转换函数。
1 | |
双重断言
当两个类型之间没有重叠关系时,TypeScript 会拒绝直接断言。此时可借助 unknown 作为中转,但这是危险操作,应尽量避免。双重断言会完全绕过 TypeScript 的类型系统,极易引发运行时错误,仅在处理第三方库类型不准确等特殊情况下使用,并在代码中注明原因。
1 | |
断言方式对比
| 断言方式 | 语法 | 典型场景 | 风险 |
|---|---|---|---|
| as 断言 | value as Type | any 转具体类型、DOM 操作 | 低(有重叠检查) |
| 非空断言 | value! | 确认非 null/undefined | 中(跳过空值检查) |
| 常量断言 | value as const | 字面量常量、枚举值 | 无 |
| 双重断言 | value as unknown as T | 类型无重叠时强制转换 | 高(完全绕过检查) |
类型断言总结
- 语法选择: 统一使用
as语法,避免在 JSX 中使用尖括号写法。 - 断言 vs 转换: 断言仅影响编译期类型,不产生运行时代码,真正的值转换需使用
Number()、String()等函数。 - 非空断言: 使用
!前确保值真的非空,否则运行时会报错,条件允许时优先用?.。 - 常量断言:
as const是安全且实用的断言,推荐用于配置对象和常量数组。 - 双重断言: 是最后手段,使用时必须注释说明原因。
总结
TypeScript 的类型系统是其相对于 JavaScript 的核心优势。本章覆盖了以下要点:
- 基础类型:
string、number、boolean、array、tuple、enum、any、void、null、undefined、never、object、联合类型、unknown、字面量类型构成了完整的类型体系。 - 变量声明:
var/let/const三种声明方式各有适用场景,配合类型注解和解构语法,可以灵活而安全地声明变量。 - 特殊类型:
any、unknown、void、never各有用途,类型安全等级从高到低为never>void>unknown>any,应优先使用更严格的类型。 - null/undefined: 启用
strictNullChecks后,使用联合类型、可选参数、可选链?.和空值合并??安全处理空值。 - Symbol: 唯一标识符,用于避免属性名冲突和创建"私有"属性。
- Number/String: 推荐使用原始类型
number/string而非包装对象Number/String。 - 类型推断: 编译器自动推断类型,减少冗余声明,但在关键位置应显式声明。
- 类型断言:
as语法手动指定类型,配合as const、!等机制灵活控制类型检查。
掌握这些基础,就为后续学习函数、接口、泛型、类等高级特性打下了坚实基础。

