基本数据类型
本章涵盖 TypeScript 中所有基础类型:原始类型(string/number/boolean)、复合类型(array/tuple/enum)、顶部/底部类型(any/unknown/never)以及字面量类型,帮助你掌握 TS 类型系统的基本单元。
类型声明方式
TypeScript 使用冒号语法 : type 标注变量类型,同时具备类型推断能力,多数情况下无需显式注解:
let name: string = "Willow"
const age: number = 18
// TS 自动推断类型
let inferred = "hello" // let inferred: string
const frozen = 42 // const frozen: 42 (字面量类型,详见下文)
💡 最佳实践:优先让 TS 自动推断类型,只在推断结果不够精确或函数参数/返回值等关键边界处显式注解。减少不必要的类型声明,让代码更简洁易读。
string — 字符串
let title: string = "Hello TypeScript"
let name = "Willow" // 推断为 string
// 模板字符串
let greeting = `Hello, ${name}!` // "Hello, Willow!"
let multiline = `
多行字符串
支持换行和 ${name}
`number — 数值
number 统一表示整数、浮点数、NaN 和 Infinity,没有 Java 那样的 int/float 之分:
let age: number = 18
let price = 9.99 // 推断为 number
let negative = -1
let nan = NaN // ✅ 合法的 number
let infinity = Infinity // ✅ 合法的 number
🔬 深入原理:TypeScript 的
number对应 JavaScript 的 IEEE 754 双精度浮点数(64-bit),所有数值 — 包括整数 — 底层都是浮点。因此0.1 + 0.2 !== 0.3这类 JS 精度问题同样存在。
bigint 用于表示任意精度的大整数,详见下文。
boolean — 布尔值
let isDone: boolean = true
let hasError = false // 推断为 boolean
array — 数组
两种等价写法,T[] 更常用:
let list: number[] = [1, 2, 3]
let names: Array<string> = ["Willow", "Ash"] // 泛型写法
// 自动推断联合类型
let mixed = [1, "hello"] // (string | number)[]
只读数组禁止修改元素:
let readonlyList: ReadonlyArray<number> = [1, 2, 3]
// readonlyList.push(4) // ❌ error
// readonlyList[0] = 0 // ❌ error
也可以用 readonly T[] 语法(效果等价):
let roList: readonly number[] = [1, 2, 3]tuple — 元组
元组是固定长度、各位置类型明确的数组:
let tuple: [string, number] = ["hello", 42]
// tuple = [42, "hello"] // ❌ 类型不匹配
可选元素
let opt: [string, number?] = ["hello"] // ✅ 第二个元素可省略
opt = ["hello", 42] // ✅
具名元组(仅用于文档提示,不影响行为)
let http: [status: number, body: string] = [200, "OK"]
// 在编辑器中会显示 status / body 标签,但运行时仍是数字索引
剩余元素
let rest: [string, ...number[]] = ["hello", 1, 2, 3]
let labels: [number, ...string[]] = [1, "a", "b", "c"]enum — 枚举
枚举用于定义一组有意义的命名常量集合。
数字枚举
默认从 0 开始自增:
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}设置起始值后继续自增:
enum Status {
Active = 1,
Inactive, // 2
Pending // 3
}可以手动指定任意成员的值:
enum Flags {
A = 1,
B = 2,
C = 4,
D = 8
}字符串枚举
每个成员必须用字符串字面量初始化,无自增行为:
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}异构枚举(不推荐)
混合字符串和数字,每个成员均须初始化:
enum Mixed {
No = 0,
Yes = "YES"
}🚨 陷阱:异构枚举降低可读性,且 TypeScript 团队表示可能在未来版本中收紧对异构枚举的支持,应避免使用。
反向映射
仅数字枚举支持反向映射 — 通过值获取键名:
enum Color {
Red, // 0
Green, // 1
Blue // 2
}
console.log(Color.Red) // 0
console.log(Color[0]) // "Red" ✅ 反向映射
🔬 深入原理:数字枚举编译后会生成一个双向映射的 IIFE 对象
{ Red: 0, 0: "Red", Green: 1, 1: "Green", ... }。字符串枚举不生成反向映射。
const enum — 常量枚举
编译时内联为字面量,不生成运行时对象:
const enum Animal {
Cat,
Dog
}
let pet = Animal.Cat // 编译后: let pet = 0
💡 最佳实践:
const enum消除运行时枚举对象开销,适合性能敏感或库体积敏感的场合。但如果其他项目可能通过--isolatedModules引用你的代码,应配合preserveConstEnums使用或改用普通 enum。
🚨 陷阱:
const enum在编译后被擦除,无法通过字符串索引反向映射,也无法被其他模块通过import正常引用枚举对象本身。单独编译或使用isolatedModules时可能出错。
枚举的运行时行为
普通 enum 编译为 IIFE(立即调用函数表达式)对象:
// 源码
enum Color { Red, Green, Blue }
// 编译后(ES5)
var Color;
(function (Color) {
Color[Color["Red"] = 0] = "Red";
Color[Color["Green"] = 1] = "Green";
Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));enum vs as const + union type
现代 TypeScript 项目中,很多场景可以用 as const 搭配联合类型替代 enum:
// enum 方式
enum Direction {
Up = "UP",
Down = "DOWN"
}
function move(dir: Direction) { /* ... */ }
move(Direction.Up)
// as const + union 方式(零 JS 开销)
const DIRECTIONS = ["UP", "DOWN"] as const
type Direction = (typeof DIRECTIONS)[number] // "UP" | "DOWN"
function move(dir: Direction) { /* ... */ }
move("UP")💡 最佳实践:当枚举仅用于字符串常量集合时,优先使用
as const+ union type,避免额外 JS 代码生成。当需要反向映射、迭代枚举值或映射到数字标记位时,再使用 enum。
symbol — Symbol
每个 Symbol() 创建的值都是独一无二的,即使描述相同:
const s1: symbol = Symbol("desc")
const s2: symbol = Symbol("desc")
// s1 !== s2 ✅ 每次调用都创建新的唯一符号
unique symbol
const 声明的 symbol 会被推断为更窄的 unique symbol 类型:
const ID: unique symbol = Symbol("id")
// 可以直接用作类型注解
let id: typeof ID由于每个 unique symbol 的类型都不同,可以用作对象的属性键,避免命名冲突:
const STATUS: unique symbol = Symbol("status")
let obj = {
[STATUS]: "active",
status: "inactive" // 与上面的属性完全独立,不会冲突
}
console.log(obj[STATUS]) // "active"
console.log(obj.status) // "inactive"
🔬 深入原理:
unique symbol是 TypeScript 类型系统中的名义类型(nominal type),两个分别声明的unique symbol在类型层面永远不兼容,即使它们的运行时描述相同。而symbol是结构类型。
bigint — 大整数
bigint 表示任意精度的大整数,突破 number 的 2^53 - 1 精度限制:
const big: bigint = 9007199254740991n
const big2: bigint = BigInt(100)
const huge = 123456789012345678901234567890n // 推断为 bigint
🚨 陷阱:
bigint和number不能直接混用进行算术运算:// let x = 1n + 1 // ❌ 类型不兼容 let x = 1n + BigInt(1) // ✅ 必须先转换此外
bigint不支持Math对象的方法(如Math.abs)。
对象字面量类型
直接用字面量语法标注对象结构:
let user: { name: string; age: number } = {
name: "Willow",
age: 18
}可选属性
let user: {
name: string
age: number
email?: string // 可选,可以不传
} = { name: "Willow", age: 18 }readonly 属性
let config: {
readonly host: string
port: number
} = { host: "localhost", port: 8080 }
// config.host = "other" // ❌ 只读属性不可修改
索引签名
允许对象拥有任意数量的动态属性:
let dict: { [key: string]: string } = {
name: "Willow",
city: "Beijing"
}
// 只限制值类型,键名可以任意添加
dict.country = "China" // ✅
💡 最佳实践:内联对象字面量类型适合简单的一次性标注。对于需要在多处复用的复杂类型,推荐使用
type别名或interface定义(详见06-接口与类型别名)。
any — 任意类型
any 是顶部类型,关闭对该变量的所有类型检查,等于是退回到了 JavaScript:
let anything: any = "hello"
anything = 42 // ✅ 不报错
anything.foo.bar() // ✅ 不报错,但运行时可能崩溃
anything() // ✅ 不报错,但运行时可能崩溃
编译选项 noImplicitAny(strict 默认启用)禁止隐式推断为 any:
// 开启 noImplicitAny 后 ❌
// function log(msg) { // 报错:参数隐式 any
// console.log(msg)
// }
function log(msg: string) { // ✅ 显式注解
console.log(msg)
}🚨 陷阱:
any会关闭所有类型检查,等于退回 JavaScript。能用unknown就不用any,能用具体类型就不用unknown。
unknown — 安全的顶部类型
unknown 也是顶部类型(任何值都可以赋给它),但使用前必须进行类型收窄:
let value: unknown = "hello"
// let str: string = value // ❌ unknown 不能直接赋给其他类型
// value.toUpperCase() // ❌ 不能直接调用方法
// 必须收窄后才能使用
if (typeof value === "string") {
let str: string = value // ✅ 类型已收窄为 string
console.log(value.toUpperCase())
}类型断言收窄
let data: unknown = { name: "Willow" }
let name = (data as { name: string }).nameany vs unknown 对比
| 特性 | any |
unknown |
|---|---|---|
| 任意类型可赋值给它 | 是 | 是 |
| 可赋值给任意类型 | 是(不检查) | 否(必须先收窄) |
| 可直接调用方法/属性 | 是 | 否 |
| 类型安全 | 否,完全绕过检查 | 是,强制收窄 |
💡 最佳实践:当不确定类型(如 JSON 解析、API 返回值)时,标注为
unknown而非any,强制自己在使用前做类型检查,从源头减少运行时 bug。
null 和 undefined
在 strictNullChecks 开启(strict: true 默认开启)时,null 和 undefined 只能赋值给自身或 void:
let u: undefined = undefined
let n: null = null
// let str: string = null // ❌ strictNullChecks 下报错
// let num: number = undefined // ❌ strictNullChecks 下报错
如果需要允许空值,显式使用联合类型:
let name: string | null = null // ✅ 允许 null
let age: string | undefined = undefined // ✅ 允许 undefined
🚨 陷阱:通过
document.querySelector等 DOM API 获取的返回值天然包含null,不要忘记判空处理:const el = document.querySelector<HTMLDivElement>(".box") if (el) { console.log(el.id) } // ✅ 安全 // console.log(el!.id) // 非空断言(!),仅在确定不为 null 时使用
void — 空类型
void 表示函数没有返回值,或返回值的类型不重要:
function log(msg: string): void {
console.log(msg)
// 没有 return 语句
}
function greet(name: string): void {
console.log("Hello, " + name)
return // ✅ 空 return 也合法
}🔬 深入原理:
void类型的函数在运行时实际返回undefined。在严格模式下void只能被赋值为undefined;关闭strictNullChecks时也可以赋值为null。
void 也可以用于变量声明(但几乎没有实用价值):
let unusable: void = undefined
// let x: void = null // ❌ strictNullChecks 下报错
never — 底部类型
never 是类型系统的底部类型:没有任何值属于 never,never 是所有类型的子类型。
抛出异常的函数
function throwError(message: string): never {
throw new Error(message)
}无限循环的函数
function infiniteLoop(): never {
while (true) { /* ... */ }
}穷尽性检查
never 最实用的场景:在 discriminated union 的 switch 中确保覆盖所有分支:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number }
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2
case "square": return s.side ** 2
default:
// 如果未来新增了 Shape 变体但忘记处理,
// 这里会编译报错,因为 s 不能赋给 never
const _exhaustive: never = s
return _exhaustive
}
}如果某人日后为 Shape 添加了 { kind: "triangle"; base: number; height: number } 但忘记在 area() 中添加 case,default 分支中的 s 就是 { kind: "triangle"; ... },无法赋给 never,编译报错 — 从而在开发阶段捕获遗漏。
💡 最佳实践:在所有需要穷尽处理的 discriminated union 中使用
never做兜底检查,这是 TypeScript 类型系统最强大的自检手段之一。
字面量类型
字面量可以直接作为类型,指定变量只能取某个精确值。
字符串字面量
let direction: "left" = "left"
// direction = "right" // ❌ 只能是 "left"
const 声明的字符串会被推断为字面量类型,let 则推断为更宽泛的 string:
const name = "Willow" // const name: "Willow" (字面量)
let nickname = "Willow" // let nickname: string (宽泛)
数字字面量
let one: 1 = 1
// one = 2 // ❌ 只能是 1
布尔字面量
let isTrue: true = true
// isTrue = false // ❌ 只能是 true
联合字面量 — 类型安全的选项
字面量最常见的用法是与 | 组合,限制取值范围:
type Direction = "up" | "down" | "left" | "right"
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6
type Response = { status: "ok"; data: unknown } | { status: "error"; message: string }
function move(dir: Direction) { /* ... */ }
move("up") // ✅
// move("north") // ❌ 不在联合中
关于字面量类型的深入用法(模板字面量类型、条件类型中的字面量等),参见
01-核心概念和08-高级类型。
常见陷阱汇总
| 陷阱 | 说明 | 正确做法 |
|---|---|---|
any 随意使用 |
关闭所有类型检查,失去 TS 的保护 | 能用 unknown 就不用 any,能用具体类型就不用 unknown |
bigint 与 number 混用 |
1n + 1 直接报错 |
使用 BigInt() 统一转换后再运算 |
| 异构 enum | 混合字符串和数字,降低可读性 | 统一为数字枚举或字符串枚举 |
const enum 跨项目引用 |
isolatedModules 下可能编译失败 |
配合 preserveConstEnums 或改用普通 enum |
strictNullChecks 关闭 |
null/undefined 随处可赋值,埋下 NPE 隐患 | 始终开启,显式使用 | null 标注可空类型 |
unknown 不经收窄直接使用 |
unknown 不能像 any 一样直接操作 |
先通过 typeof、instanceof 或 as 收窄 |