TypeScript: TypeScript 变量与类型推断
最后更新:2026-08-26
TypeScript 最强大的能力之一是"类型推断"——你不需要到处写类型注解,编译器自动帮你推断。但理解推断规则,才能写出让编译器正确理解的代码。
1. 变量声明:let、const 和 var
TypeScript 完全继承了 JavaScript 的三种变量声明方式,但推荐使用 let 和 const:
(1) let —— 可重新赋值的变量
TYPESCRIPT
let count = 0;
count = 1; // ✅ let 变量可以重新赋值
(2) const —— 不可重新赋值的常量
TYPESCRIPT
const MAX_SIZE = 100;
MAX_SIZE = 200; // ❌ 错误:不能赋值给常量
(3) var —— 函数作用域(不推荐)
TYPESCRIPT
var x = 1; // ⚠️ 可用但不推荐——var 有变量提升和作用域泄漏问题
📌 重点: 永远用
let 和 const,不用 var。能用 const 就用 const(值不会变时),需要重新赋值才用 let。这和 JavaScript 最佳实践完全一致。
(4) 类型注解 vs 类型推断
TYPESCRIPT
// 类型注解——你告诉 TypeScript 变量是什么类型
let age: number = 25;
// 类型推断——TypeScript 根据初始值自动判断类型
let name = "Charlie"; // TypeScript 推断为 string
let active = true; // TypeScript 推断为 boolean
两种写法效果相同,但有初始值时推荐省略注解,让推断生效。
2. 类型推断的完整规则
类型推断不是"猜"——它有明确的规则。理解规则才能预测编译器的行为。
(1) 规则一:初始值推断
变量有初始值时,TypeScript 根据初始值的类型推断:
TYPESCRIPT
let message = "hello"; // 推断为 string
let count = 42; // 推断为 number
let flag = true; // 推断为 boolean
let items = [1, 2, 3]; // 推断为 number[]
(2) 规则二:const 的字面量类型推断
这是 TypeScript 推断中最精妙的细节——let 和 const 的推断结果不同:
TYPESCRIPT
// let 推断为宽类型(widening)
let x = "hello"; // 推断为 string
let n = 42; // 推断为 number
let b = true; // 推断为 boolean
// const 推断为精确的字面量类型(no widening)
const x2 = "hello"; // 推断为 "hello"(字面量类型,不是 string)
const n2 = 42; // 推断为 42(字面量类型,不是 number)
const b2 = true; // 推断为 true(字面量类型,不是 boolean)
💡 为什么这样设计?
let 变量可以重新赋值,所以推断为更宽的类型(string 可以接收任何字符串);const 变量不会变,所以推断为精确的字面量类型。这个行为叫做 widening(类型拓宽)。
(3) 规则三:无初始值推断为 any
如果变量声明时没有初始值也没有类型注解,TypeScript 推断为 any:
TYPESCRIPT
let something; // 推断为 any(危险!)
something = 42; // any,不检查
something = "hello"; // any,不检查——完全绕过了类型系统
🔥 易错:
any 等于关闭了类型检查,和写 JavaScript 没区别。开启 strict 模式后,无初始值的变量会报错(需显式标注类型或给初始值)。
(4) 规则四:最佳公共类型推断
当从多个来源推断类型时,TypeScript 会找它们的"最佳公共类型":
TYPESCRIPT
let arr = [1, "hello", true];
// 推断为 (number | string | boolean)[] —— 联合类型数组
let arr2 = [1, 2, 3];
// 推断为 number[] —— 所有元素同类型,推断为元素类型数组
▶ 示例:类型推断实验
TYPESCRIPT
// 实验1:let vs const 的推断差异
let dynamicText = "hello";
const fixedText = "hello";
dynamicText = "world"; // ✅ string 可以接收另一个字符串
// fixedText = "world"; // ❌ const 不可重新赋值
// 实验2:对象属性的推断
let user = {
name: "Charlie",
age: 20
};
// 推断为 { name: string; age: number }
user.name = "Diana"; // ✅ string 属性可以赋新值
user.age = 21; // ✅ number 属性可以赋新值
// 实验3:const 对象——属性仍然可修改!
const config = {
host: "localhost",
port: 3000
};
config.host = "127.0.0.1"; // ✅ const 只禁止重新赋值对象本身,不冻结属性
// config = { host: "x", port: 1 }; // ❌ 不能重新赋值整个对象
3. 类型兼容性基础
TypeScript 的类型兼容采用"结构化子类型"——只要结构匹配就兼容,不要求类型名称相同。
(1) 子类型兼容
TYPESCRIPT
let num: number = 42;
let big: bigint = 100n;
// num = big; // ❌ bigint 不是 number 的子类型
// big = num; // ❌ number 不是 bigint 的子类型
(2) 字面量类型是宽类型的子类型
TYPESCRIPT
let text: string = "hello"; // string 类型
const literal: "hello" = "hello"; // 字面量类型 "hello"
text = literal; // ✅ "hello" 是 string 的子类型,可以赋值
// literal = text; // ❌ string 不是 "hello" 的子类型
📌 类比: 字面量类型和宽类型的关系,就像"苹果"和"水果"——苹果是水果的一种,但水果不一定是苹果。
(3) any 是"逃生舱"
TYPESCRIPT
let a: any = "hello";
a = 42; // ✅ any 可以接收任何类型
a = true; // ✅ any 不做任何检查
let b: string = a; // ✅ any 也可以赋给任何类型(危险!)
⚠️ 警告:
any 打破了类型安全,只在以下情况使用:(1) 快速原型开发 (2) 迁移旧 JavaScript 代码 (3) 第三方库没有类型定义时的临时方案。正常开发中应避免 any。
4. 类型断言与类型推断的配合
当推断结果不是你想要的,可以用类型断言覆盖:
TYPESCRIPT
// 场景:DOM 元素的类型推断
let input = document.getElementById("myInput");
// 推断为 HTMLElement | null——不知道具体是什么元素
let inputEl = document.getElementById("myInput") as HTMLInputElement;
// 断言为 HTMLInputElement——你告诉编译器"我确定这是 input 元素"
💡 提示: 类型断言不做运行时转换,只是编译时的类型标注。如果断言错误,运行时仍然会出问题。第18课会详细讲解。
5. 实际开发中的推断策略
(1) 策略一:能推断就不写
TYPESCRIPT
// ❌ 冗余
let name: string = "Charlie";
const MAX: number = 100;
function add(a: number, b: number): number { return a + b; }
// ✅ 简洁
let name = "Charlie";
const MAX = 100;
function add(a: number, b: number) { return a + b; } // 返回值推断为 number
(2) 策略二:接口和类型别名必须写
TYPESCRIPT
interface User {
name: string;
age: number;
}
// 对象字面量赋值给带类型的变量时,推断+类型检查同时生效
let user: User = { name: "Charlie", age: 20 }; // ✅ 结构匹配
let bad: User = { name: "Charlie" }; // ❌ 缺少 age 属性
(3) 策略三:复杂表达式显式标注
TYPESCRIPT
// 复杂函数返回值——建议显式标注,避免推断错误
function getUserInfo(id: number): { name: string; age: number } | null {
if (id <= 0) return null;
return { name: "用户" + id, age: 20 };
}
▶ 示例:推断实战——配置对象
TYPESCRIPT
// 用 as const 断言让对象属性变为只读字面量类型
const THEME = {
primary: "#4A90D9",
secondary: "#7B51D9",
fontSize: 14
} as const;
// 推断结果:
// { readonly primary: "#4A90D9"; readonly secondary: "#7B51D9"; readonly fontSize: 14 }
// 每个属性都是精确的字面量类型 + readonly
// THEME.primary = "#000"; // ❌ readonly 属性不可修改
// THEME.fontSize = 16; // ❌ readonly 属性不可修改
console.log(THEME.primary); // "#4A90D9"
console.log(THEME.fontSize); // 14
输出:
TEXT
📖 仅展示
#4A90D9
14
💡 as const 的作用: 它让 TypeScript 把值推断为最精确的字面量类型(不加 widening),并加上 readonly 修饰。常用于定义常量配置、action 类型等场景。
❓ 常见问题
Q 什么时候必须手动写类型注解?
A 三种情况:(1) 函数参数——没有初始值供推断 (2) 变量声明时无初始值——否则推断为 any (3) 返回值类型不符合预期——推断结果太宽或太窄时需要覆盖。
Q let 和 const 的推断差异在实际上有什么影响?
A let 推断为宽类型(string/number 等),const 推断为字面量类型("hello"/42 等)。差异主要影响联合类型和字面量类型的匹配。例如用 const 定义字符串常量时,类型推断为精确字面量,可以作为联合类型的判别值使用。
Q any 和 TypeScript 的关系是什么?用 any 好不好?
A any 是 TypeScript 的"类型逃生舱"——它关闭类型检查,等于退回 JavaScript 模式。除非迁移旧代码或临时绕过类型问题,否则不应该用 any。如果不确定类型,用 unknown(第5课详解)比 any 更安全。
Q as const 是什么?和普通 const 有什么区别?
A 普通 const 只防止变量重新赋值,类型推断仍会 widening(
const x = "hi" 推断为 "hi" 但对象属性仍为宽类型)。as const 是类型断言,让所有层级都推断为字面量+readonly。对象/数组用 as const 才能获得真正的深度只读效果。📖 小节
- 用
let声明可变变量,const声明常量,不用var - 类型推断规则:有初始值按值推断,const 推断为字面量类型(no widening),let 推断为宽类型(widening),无初始值推断为 any
any关闭类型检查,应尽量避免;as const让推断变为精确字面量+readonly- 实际开发策略:能推断就省略,函数参数必须标注,复杂返回值建议标注
- 类型兼容遵循结构化子类型:字面量类型兼容宽类型,反之不行
📝 作业
- 基础题(难度⭐):用
let和const分别声明一个字符串变量,打印它们的值。然后尝试重新赋值,观察 TypeScript 的报错行为。 - 进阶题(难度⭐⭐):定义一个
as const的配置对象COLORS,包含 red、green、blue 三个十六进制颜色值。然后尝试修改其中一个颜色,观察 readonly 的报错效果。 - 挑战题(难度⭐⭐⭐):写一个函数
createUser(name: string, age: number),不标注返回值类型,让 TypeScript 推断返回类型。然后在外部用一个带类型的变量接收返回值,验证推断结果是否正确。