TypeScript 入门:从基础到 any、unknown、never 的实战解析
作为前端开发者,你可能听过 "JS 够用了,为什么还要学 TS?"------ 但当项目代码量突破万行、多人协作频繁时,JS 的 "灵活性" 会逐渐变成 "隐形炸弹":传错参数类型、改漏对象属性、接口返回结构混乱... 这些问题往往要到运行时才暴露,排查成本极高。
而 TypeScript(简称 TS)的核心价值,就是给 JS 加上 "类型安全网":提前在编译阶段拦截类型错误,让代码更易维护、协作更高效。今天我们就从 TS 基础入手,再深入拆解三个容易混淆的特殊类型:any、unknown、never,帮你搞懂 "什么时候该用什么类型"。
一、TS 基础:先学会 "给 JS 加类型"
TS 是 JS 的超集,所有 JS 代码能直接在 TS 中运行,我们要做的核心操作,就是给变量、函数、对象 "标注类型"------ 让 TS 知道 "这个数据应该是什么样子"。
1. 变量的类型标注
语法很简单:let 变量名: 类型 = 值。TS 甚至能 "自动推论类型"(简单场景不用手动标),但复杂场景建议显式标注,可读性更强。
typescript
// 1. 基础类型:string/number/boolean
let username: string = "张三"; // 字符串
let age: number = 28; // 数字(整数/小数都支持)
let isVip: boolean = true; // 布尔值(仅true/false)
// 2. 数组类型:两种写法
let hobbies: string[] = ["打球", "编程"]; // 推荐:类型+[]
let scores: Array<number> = [90, 85, 95]; // 泛型写法(后续会讲)
// 3. 自动推论:简单场景可省略类型标注
let city = "北京"; // TS自动推论city是string类型
city = 123; // 报错:不能将number赋给string
2. 函数的类型标注
重点标注 "参数类型" 和 "返回值类型",避免传错参数或误解返回结果。
typescript
// 语法:function 函数名(参数: 类型): 返回值类型 { ... }
function add(a: number, b: number): number {
return a + b; // 正确:返回number
}
// 可选参数:加?,表示可传可不传
function greet(name?: string): string {
return name ? `Hi ${name}` : "Hi 陌生人";
}
// 无返回值:用void标注(表示"正常执行完,但没返回值")
function logMsg(msg: string): void {
console.log(msg); // 不用return,或return undefined
}
3. 对象的类型约束(接口 Interface)
当需要定义 "对象结构" 时(比如后端返回的用户数据),用interface明确属性名和类型,避免漏传或错传属性。
typescript
// 定义用户接口:规定对象必须有哪些属性
interface User {
id: string; // 必选属性
name: string;
age?: number; // 可选属性(加?)
readonly role: string; // 只读属性(不能修改)
}
// 使用接口:对象必须符合接口结构
const user: User = {
id: "u123",
name: "李四",
role: "user" // 只读,后续不能改 user.role = "admin" 会报错
};
二、深入解析:any、unknown、never 三个特殊类型
前面的基础类型(string/number 等)很直观,但实际开发中会遇到 "不确定类型" 的场景(比如后端返回的未知数据、第三方库的复杂类型),这时候就需要 any、unknown、never 这三个特殊类型 ------ 但它们的用法和风险差异极大,用错了会让 TS 的 "类型安全" 形同虚设。
1. any:"万能但危险" 的类型
定义:表示 "任意类型",TS 会完全放弃对 any 类型的检查 ------ 你可以给 any 变量赋任何值,也可以把 any 变量赋给任何类型。
ini
let value: any = "hello";
value = 123; // 不报错:any支持任意类型赋值
value = { id: 1 }; // 仍不报错
// 风险:类型污染
let num: number = value; // 不报错:any能赋给任何类型
num.toFixed(2); // 运行时可能报错:如果value实际是对象,toFixed不存在
使用场景:
- 旧 JS 项目迁移到 TS 时,临时兼容 "无法确定类型" 的代码(过渡方案);
- 确实需要 "关闭类型检查" 的极端场景(比如动态生成的代码)。
避坑点:
- 禁止 "隐式 any":开启tsconfig.json的strict: true,当变量没标类型且 TS 无法推论时(如function fn(a) {}),会直接报错,避免无意识的类型污染;
- 能不用就不用:any 会让 TS 的 "提前纠错" 优势失效,优先用后面的 unknown 替代。
2. unknown:"安全的不确定类型"
定义 :同样表示 "不确定类型",但比 any 更安全 ------unknown 变量可以接收任何类型的值,但不能直接赋给其他类型(除了 any 和 unknown),必须先 "确认类型" 才能使用。
ini
let value: unknown = "hello";
value = 123; // 不报错:unknown支持接收任意类型
// 1. 直接赋值给其他类型:报错(安全机制)
let num: number = value; // 报错:不能将unknown赋给number
// 2. 先确认类型(类型守卫),再使用:安全
if (typeof value === "number") {
// 这里TS会自动推论value是number类型
num = value; // 不报错
console.log(num.toFixed(2)); // 安全:确定是number
}
常用类型守卫方式:
- typeof:判断基础类型(string/number/boolean 等);
- instanceof:判断引用类型(Array/Object/ 自定义类等);
- 自定义函数:判断复杂结构(如是否符合 User 接口)。
javascript
// 自定义类型守卫:判断是否为User类型
function isUser(obj: unknown): obj is User {
return (
typeof obj === "object" &&
obj !== null &&
"id" in obj &&
"name" in obj &&
"role" in obj
);
}
// 使用:先判断,再使用
if (isUser(value)) {
console.log(value.name); // 安全:确定是User类型
}
使用场景:
- 接收未知类型的数据(如后端接口返回的 JSON、用户输入的动态值);
- 替代 any,在 "不确定类型" 时保持类型安全。
核心优势:既保留了 "接收任意类型" 的灵活性,又通过 "类型守卫" 避免了类型污染,是 any 的安全替代方案。
3. never:"不存在的底层类型"
定义:表示 "不存在的类型",是 TS 的底层类型 ------ 任何值都不能赋给 never,但 never 可以赋给任何类型(类比 "空集是任何集合的子集")。
never 的场景很特殊,主要用于两种情况:
情况 1:函数永远不会正常执行结束
比如函数抛错、死循环 ------ 因为函数永远到不了 "返回" 这一步,所以返回值类型是 never(不是 void,void 表示 "正常执行完但没返回值")。
typescript
// 1. 抛错函数:执行到throw就中断,永远不返回
function error(msg: string): never {
throw new Error(msg); // 抛错后,函数没机会返回
console.log("这里永远到不了"); // 死代码,TS会提示
}
// 2. 死循环函数:永远不结束
function infiniteLoop(): never {
while (true) {} // 无限循环,永远不返回
}
情况 2:类型判断 "穷尽所有可能" 后的剩余类型
当你用联合类型做判断时,如果已经覆盖了所有可能的类型,最后 else 分支的类型就是 never------ 这是 TS 的 "穷尽检查" 能力,能帮你避免漏判。
typescript
// 定义联合类型:只有两个可能
type Status = "success" | "fail";
function handleStatus(status: Status) {
if (status === "success") {
console.log("成功");
} else if (status === "fail") {
console.log("失败");
} else {
// 此时status的类型是never:因为Status的所有可能都被覆盖了
// 如果后续给Status加了新值(如"pending"),这里会报错,提醒你补判断
const _exhaustiveCheck: never = status;
throw new Error(`未处理的状态:${_exhaustiveCheck}`);
}
}
使用场景:
- 标注 "会中断执行的函数"(抛错、死循环);
- 联合类型的 "穷尽检查",避免漏判类型(尤其适合枚举或固定选项的场景)。
三、对比总结:any、unknown、never 该怎么选?
| 类型 | 核心特点 | 安全程度 | 适用场景 |
|---|---|---|---|
| any | 放弃类型检查,可赋给任何类型 | 极低 | 旧项目迁移、极端需关闭检查的场景 |
| unknown | 需先确认类型才能使用,不能直接赋值给其他类型 | 高 | 接收未知类型数据(如接口返回、用户输入) |
| never | 不存在的类型,任何值不能赋给它 | 极高 | 函数中断执行、联合类型穷尽检查 |
一句话选型建议:
- 不确定类型时,优先用unknown(安全),别用any(危险);
- 函数抛错或死循环时,用never标注返回值;
- 联合类型判断时,用never做穷尽检查,避免漏判。
四、最后:TS 入门的核心原则
- 别追求 "全学会再用" :TS 知识点多,但前端开发常用的只有 "基础类型 + 接口 + unknown + 泛型基础",先掌握这些就能应对 80% 场景;
- 开启 strict 模式:tsconfig.json的strict: true能强制你写更规范的类型,避免隐式 any 等坑;
- 类型是 "辅助工具" 不是 "束缚" :TS 的目标是帮你少踩坑,不是让你花大量时间写复杂类型 ------ 能清晰表达意图的类型就是好类型。
如果刚开始用 TS 觉得 "麻烦",别担心:写过 1-2 个小项目后,你会发现 "提前标注类型" 比 "后期排查类型 bug" 省力多了。希望这篇文章能帮你快速入门 TS,避开 any、unknown、never 的混淆坑~