大白话 TypeScript 中的类型有哪些?请列举。
前端小伙伴们,有没有被"类型错误"搞到抓头发过?写个函数传参,把number写成string,运行时才报错;定义对象少了个属性,测试时页面直接白屏……今天咱们就盘一盘TypeScript的"类型家族",从基础到高级,用最接地气的话讲清每种类型的作用,看完这篇,你不仅能叫出所有类型的名字,还能和面试官唠明白它们的使用场景~
一、"类型盲写"的痛与救
先讲个我上周踩的坑:给客户做表单验证组件,定义了一个validateUser函数,预期接收{ name: string; age: number }类型的参数。结果同事传了个age: "25"(字符串),页面直接报NaN错误。要是用了TypeScript,编辑器会红色波浪线警告,提前避免这种低级错误!
这种"类型盲写"的痛点,本质是JavaScript的"弱类型"特性——变量类型可以随意变,代码量大时根本记不住每个变量该是什么类型。而TypeScript的类型系统就是来当"代码安检员"的:写代码时就检查类型错误,把问题消灭在萌芽阶段。
二、TypeScript类型的"家族树"
TypeScript的类型系统像一棵枝叶繁茂的树,根是"基础类型",向上生长出"复合类型",再衍生出"高级类型"。咱们按层级逐个拆解:
1. 基础类型:最常用的"原子类型"
基础类型是TypeScript类型系统的"地基",对应JavaScript的原始类型+几个扩展类型,特点是不可再拆分。
类型名描述示例string字符串类型(单/双引号或模板字符串)const name: string = "张三";number数字类型(整数、浮点数、NaN、Infinity等)const age: number = 25;boolean布尔类型(true或false)const isVip: boolean = true;null空值(表示"没有值",只能赋值给null类型)const empty: null = null;undefined未定义(变量声明但未赋值时的默认值)const unassigned: undefined = undefined;symbol唯一且不可变的标识符(ES6新增,用于对象属性键)const key: symbol = Symbol("unique");bigint大整数类型(超过Number.MAX_SAFE_INTEGER的整数,用n后缀)const largeNum: bigint = 100000000000000000000n;void“无类型”(通常用于函数无返回值)function log(): void { console.log("hi"); }never“永不存在的值”(函数永远不会结束或抛出异常)function error(msg: string): never { throw new Error(msg); }2. 复合类型:基础类型的"组合体"
复合类型由基础类型或其他复合类型组合而成,描述更复杂的数据结构,常见的有:
(1)数组类型
描述"同类型元素的集合",有两种写法:
类型[](如number[]表示数字数组);Array<类型>(如Array
// 数字数组(两种写法等价)
const nums1: number[] = [1, 2, 3];
const nums2: Array
// 对象数组(每个元素是{ name: string }类型)
const users: { name: string }[] = [
{ name: "张三" },
{ name: "李四" }
];
(2)元组(Tuple)
描述"固定长度、类型确定的数组",元素类型可以不同(和数组的区别:长度和类型固定)。
// 元组:[字符串, 数字, 布尔]
const person: [string, number, boolean] = ["张三", 25, true];
person[0] = "李四"; // 正确(string类型)
person[1] = "25"; // 错误(不能将string赋给number)
(3)对象类型
描述"键值对集合",明确每个属性的类型(可选属性用?标记,只读属性用readonly)。
// 对象类型(包含可选属性和只读属性)
type User = {
name: string; // 必选属性(string类型)
age?: number; // 可选属性(number类型,可不存在)
readonly id: number; // 只读属性(赋值后不可修改)
};
const user: User = {
name: "张三",
id: 1001
};
user.age = 25; // 正确(可选属性赋值)
user.id = 1002; // 错误(只读属性不可修改)
(4)枚举(Enum)
为一组数值或字符串值赋予友好的名字(解决"魔法数字"问题,比如用Status.Enable代替1)。
// 数字枚举(自动赋值,第一个成员默认0,后续递增)
enum Status {
Disable, // 0
Enable, // 1
Deleted // 2
}
console.log(Status.Enable); // 输出:1
// 字符串枚举(每个成员必须手动赋值)
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
console.log(Direction.Left); // 输出:"LEFT"
3. 高级类型:类型系统的"魔法工具"
高级类型是TypeScript的"进阶武器",可以动态生成新类型,解决复杂场景的类型约束问题。
(1)类型别名(Type Alias)
给类型起一个别名,简化复杂类型的使用(用type关键字定义)。
// 为对象类型起别名
type Point = { x: number; y: number };
const p: Point = { x: 10, y: 20 };
// 为联合类型起别名(后面会讲)
type ID = number | string;
const userId: ID = "1001"; // 可以是number或string
(2)接口(Interface)
描述对象的结构(和类型别名类似,但更侧重"行为约定",常用于面向对象设计)。
// 接口定义"可打印"行为
interface Printable {
content: string;
print(): void; // 方法(无具体实现)
}
// 类实现接口(必须包含content属性和print方法)
class Document implements Printable {
content: string;
constructor(content: string) {
this.content = content;
}
print() {
console.log(this.content);
}
}
(3)联合类型(Union Type)
表示"可以是多种类型中的一种"(用|分隔,解决"参数可以是A或B"的场景)。
// 函数参数可以是number或string(联合类型)
function formatID(id: number | string): string {
return id.toString(); // 安全操作(number和string都有toString方法)
}
formatID(1001); // 正确
formatID("1001"); // 正确
(4)交叉类型(Intersection Type)
表示"多种类型的交集"(用&合并,解决"同时具备A和B属性"的场景)。
// 定义"用户"和"权限"类型
type User = { name: string };
type Permission = { role: "admin" | "user" };
// 交叉类型:同时具备User和Permission的属性
type AdminUser = User & Permission;
const admin: AdminUser = {
name: "管理员",
role: "admin"
};
(5)泛型(Generic)
定义"类型模板",让组件支持多种类型(解决"函数/类/接口能处理不同类型"的问题)。
// 泛型函数:返回传入的值(保持类型)
function identity
return arg;
}
const num: number = identity(123); // T推断为number
const str: string = identity("abc"); // T推断为string
// 泛型接口:定义"容器"类型
interface Container
value: T;
getValue(): T;
}
const stringContainer: Container
value: "hello",
getValue() { return this.value; }
};
(6)条件类型(Conditional Type)
根据类型关系动态选择类型(用T extends U ? X : Y语法,类似JS的三元表达式)。
// 条件类型:判断是否是数组类型
type IsArray
type A = IsArray
type B = IsArray
(7)类型断言(Type Assertion)
告诉编译器"我知道这个值的类型,相信我"(解决编译器无法推断类型的场景,用as或<>语法)。
// 从DOM获取元素(编译器无法确定具体类型)
const el = document.getElementById("app");
const divEl = el as HTMLDivElement; // 断言为HTMLDivElement类型
divEl.style.color = "red"; // 可以访问div特有的style属性
(8)类型守卫(Type Guard)
在运行时检查类型,缩小类型范围(用typeof/instanceof/自定义函数,解决联合类型的细分问题)。
// 联合类型:string或number
function printValue(value: string | number) {
// typeof类型守卫(缩小为string)
if (typeof value === "string") {
console.log("字符串长度:", value.length);
}
// typeof类型守卫(缩小为number)
if (typeof value === "number") {
console.log("数字平方:", value * value);
}
}
三、一张表理清类型分类
类型大类子类型核心特点典型场景基础类型string/number等不可再拆分,对应JS原始类型+扩展类型变量、函数参数的基础类型约束复合类型数组/元组/对象/枚举由基础类型组合而成,描述复杂数据结构列表数据、固定格式数据、状态标记高级类型类型别名/接口/联合等动态生成类型,解决复杂场景的类型约束通用组件、类型逻辑判断、类型收缩四、面试题回答方法
正常回答(结构化):
“TypeScript的类型可分为三大类:
基础类型:包括string、number、boolean、null、undefined、symbol、bigint、void、never,对应JavaScript的原始类型和扩展类型;复合类型:由基础类型组合而成,如数组(number[])、元组([string, number])、对象({ name: string })、枚举(enum);高级类型:动态生成类型的工具,如类型别名(type)、接口(interface)、联合类型(A | B)、交叉类型(A & B)、泛型(T)、条件类型(T extends U ? X : Y)、类型断言(as)、类型守卫(typeof)等。”
大白话回答(接地气):
“TypeScript的类型就像咱们整理衣柜——
基础类型是‘单件衣服’:T恤(string)、裤子(number)、袜子(boolean),都是最基础的单品;复合类型是‘搭配套装’:运动套装(数组,同类型元素)、礼服套装(元组,固定类型和数量)、带标签的盒子(对象,键值对);高级类型是‘魔法衣模’:可以按需求调整大小的万能衣架(泛型)、判断衣服类型的小镜子(类型守卫)、把两件衣服拼成新款式的缝纫机(交叉类型)。”
五、总结:3个使用原则+2个避坑指南
3个使用原则:
基础类型优先:变量、参数的基础类型约束用基础类型(如name: string);复合类型描述结构:复杂数据(如对象、列表)用复合类型(如User[]、[string, number]);高级类型解决复杂问题:通用组件用泛型,类型判断用条件类型,类型收缩用类型守卫。
2个避坑指南:
避免过度使用any:any会关闭类型检查(回到JS的"弱类型"模式),除非万不得已(如兼容旧代码),否则不要用;
错误示例:
const data: any = fetchData(); // 放弃类型检查,可能引入错误
接口和类型别名选对场景:
描述对象结构/行为约定(如类实现)→ 用接口(interface);定义联合/交叉类型、简化复杂类型→ 用类型别名(type)。
六、扩展思考:4个高频问题解答
问题1:interface和type有什么区别?
解答:
扩展方式:接口可以继承(extends),类型别名不能(但可以通过交叉类型&模拟);重复声明:接口可以重复声明(合并属性),类型别名不能(重复声明报错);使用场景:接口更适合描述对象的结构和行为(如类实现),类型别名更灵活(支持联合、交叉等)。
示例(接口合并):
interface User { name: string }
interface User { age: number } // 合并为{ name: string; age: number }
const user: User = { name: "张三", age: 25 }; // 正确
问题2:never和void有什么区别?
解答:
void表示"没有返回值"(函数可能返回undefined);never表示"永远不会结束"(函数抛出异常或无限循环)。
示例:
function log(): void {
console.log("hi");
// 返回undefined(隐式)
}
function error(msg: string): never {
throw new Error(msg);
// 永远不会执行到这里
}
问题3:泛型T和any有什么区别?
解答:
any会关闭类型检查(类型不安全);泛型T保留类型信息(类型安全),编译器会推断具体类型。
示例:
// any版本(类型不安全)
function identityAny(arg: any): any {
return arg;
}
const num = identityAny("123"); // num类型是any,无法保证是number
// 泛型版本(类型安全)
function identityGeneric
return arg;
}
const str = identityGeneric("123"); // str类型是string(编译器推断)
问题4:类型断言和类型转换有什么区别?
解答:
类型断言:仅告诉编译器"相信我,这个值是XX类型"(不改变运行时行为);类型转换(JS的Number()/String()):实际改变值的类型(影响运行时)。
示例:
// 类型断言(不改变值)
const str = "123" as number; // 编译通过,但运行时还是string(可能报错)
// 类型转换(改变值)
const num = Number("123"); // 运行时转为number(123)
七、结尾:类型系统,代码的"安全气囊"
TypeScript的类型系统就像开车时的安全气囊——平时感觉不到它的存在,但关键时刻能避免"翻车"(类型错误)。掌握各种类型的特点和使用场景,你就能写出更健壮、易维护的代码,面试时也能轻松应对类型相关的问题~
下次写代码时,记得让TypeScript的类型检查帮你"把好第一关"!如果这篇文章帮你理清了思路,记得点个收藏,咱们下期聊,不见不散!