Skip to content
基本数据类型

基本数据类型

本章涵盖 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

🚨 陷阱bigintnumber 不能直接混用进行算术运算:

// 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()                        // ✅ 不报错,但运行时可能崩溃

编译选项 noImplicitAnystrict 默认启用)禁止隐式推断为 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 }).name

any vs unknown 对比

特性 any unknown
任意类型可赋值给它
可赋值给任意类型 是(不检查) 否(必须先收窄)
可直接调用方法/属性
类型安全 否,完全绕过检查 是,强制收窄

💡 最佳实践:当不确定类型(如 JSON 解析、API 返回值)时,标注为 unknown 而非 any,强制自己在使用前做类型检查,从源头减少运行时 bug。


null 和 undefined

strictNullChecks 开启(strict: true 默认开启)时,nullundefined 只能赋值给自身或 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 是类型系统的底部类型:没有任何值属于 nevernever 是所有类型的子类型。

抛出异常的函数

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
bigintnumber 混用 1n + 1 直接报错 使用 BigInt() 统一转换后再运算
异构 enum 混合字符串和数字,降低可读性 统一为数字枚举或字符串枚举
const enum 跨项目引用 isolatedModules 下可能编译失败 配合 preserveConstEnums 或改用普通 enum
strictNullChecks 关闭 null/undefined 随处可赋值,埋下 NPE 隐患 始终开启,显式使用 | null 标注可空类型
unknown 不经收窄直接使用 unknown 不能像 any 一样直接操作 先通过 typeofinstanceofas 收窄