TypeScript学习-第3章:复合类型
各位前端工友们,上一章咱们搞定了基础类型,相当于摸清了TS世界里的"单个零件"------字符串、数字这些独立个体。但实际开发中,咱们面对的都是"组装件":比如用户列表是多个用户对象组成的集合,一条订单信息里既有字符串姓名,又有数字金额。
这一章咱们就解锁"零件组装技能"------复合类型,核心搞定数组、元组、对象这三大主力,学会给复杂数据结构"贴精准标签"。如果说基础类型是"单兵作战",那复合类型就是"团队协作",掌握它们才能应对真实业务场景。
一、数组类型:同类元素的"整齐队列"
数组咱们在JS里天天用,本质是"一组相同类型的数据集合"。TS给数组加类型约束,就是给这个队列定规矩:"所有人必须是同一种身份",避免混进"异类"。咱们先讲两种声明方式,再聊只读数组的特殊用法。
1. 两种声明方式:各有适配场景
TS里数组有两种标注风格,按需选择即可,核心效果一致:
- 简洁式:type[] ------日常开发首选,写法清爽。在基础类型后面加
[],就表示"该类型的数组"。
typescript
// 数字数组:只能存数字
let scores: number[] = [90, 85, 95];
// 字符串数组:只能存字符串
let names: string[] = ["张三", "李四", "王五"];
// 错误示例:混存不同类型会标红
// let mixArr: number[] = [10, "20"]; // 类型不匹配
- 泛型式:Array<type> ------基于泛型语法(后续章节会深入),适合复杂场景(如嵌套泛型)。写法是
Array后跟尖括号,里面填元素类型。
typescript
// 数字数组,和number[]等价
let scores: Array<number> = [90, 85, 95];
// 字符串数组,和string[]等价
let names: Array<string> = ["张三", "李四", "王五"];
小提醒:日常开发用type[]就够了,只有当数组元素是泛型类型(比如后续的Array<Array<number>>二维数组)时,泛型式会更易读。
2. 只读数组:不能修改的"固定队列"
有些场景下,数组创建后就不能增删改元素(比如接口返回的固定列表),这时候就需要"只读数组",相当于给队列上了"锁",禁止任何修改操作。有两种声明方式:
-
readonly type[] ------简洁式,在类型前加
readonly。typescript// 只读字符串数组 let readonlyNames: readonly string[] = ["张三", "李四"]; // 以下操作都会报错:只读数组禁止修改 // readonlyNames.push("赵六"); // 禁止新增 // readonlyNames[0] = "张小三"; // 禁止修改元素 // readonlyNames.pop(); // 禁止删除 -
ReadonlyArray<type>------泛型式,和上面效果完全一致。
typescriptlet readonlyScores: ReadonlyArray<number> = [90, 85]; // 同样禁止所有修改操作 // readonlyScores[1] = 90; // 报错
避坑点:只读数组只是"禁止修改元素和长度",如果元素是对象(引用类型),对象内部的属性仍可修改(后续对象部分会细说)。比如readonly {name: string}[],数组不能增删,但里面对象的name可以改。
二、元组(Tuple):固定"人数"和"身份"的队列
数组是"同类元素的可变长队列",而元组是"固定长度、固定类型顺序的严格队列"------相当于规定了"队列里必须有n个人,第1个是A身份,第2个是B身份,不能多也不能少"。这种特性特别适合处理"结构固定的短数据"。
1. 元组的基础用法
声明语法:用[类型1, 类型2, ...]列出每个位置的类型,赋值时必须严格对应------长度一致、类型顺序一致。
// 元组:第1个元素是字符串(姓名),第2个是数字(年龄),第3个是布尔(是否成年)
let person: [string, number, boolean] = ["张三", 25, true];
// 正确访问:按索引取对应类型的值
let name: string = person[0];
let age: number = person[1];
// 错误示例:类型不匹配或长度不一致
// let wrongPerson1: [string, number, boolean] = ["李四", "26", true]; // 第2个类型错误
// let wrongPerson2: [string, number, boolean] = ["李四", 26]; // 长度不足
2. 元组的避坑点与实用场景
元组看似简单,但有两个容易踩的坑,还有几个高频实用场景,咱们逐一梳理:
-
越界赋值的隐患 :虽然元组长度固定,但在非严格模式下,用
push方法新增元素不会报错(严格模式下会警告),但新增元素的类型会被约束为元组所有类型的联合类型。typescriptlet person: [string, number] = ["张三", 25]; person.push(30); // 非严格模式下不报错,新增元素必须是string或number // person.push(true); // 报错:布尔值不属于元组类型的联合类型`建议:用元组就尽量保持"固定长度",避免手动增删元素,开启严格模式可减少此类隐患。 -
可选元素(TS 4.0+支持) :元组也可以有可选元素,用
?标记,可选元素必须放在最后。// 第3个元素(手机号)是可选的 let user: [string, number, string?] = ["张三", 25]; user = ["李四", 26, "13800138000"]; // 也可以补全可选元素 -
实用场景:元组适合处理"结构固定的短数据",比如接口返回的坐标([x, y])、用户的账号密码组合、函数返回的多值结果等。
// 场景1:坐标(x是数字,y是数字) let position: [number, number] = [100, 200]; // 场景2:函数返回多值(成功状态+数据) function getUser(): [boolean, {name: string, age: number}] { return [true, {name: "张三", age: 25}]; }
三、对象类型:多类型元素的"组合体"
对象是TS里最常用的复合类型,本质是"键值对的集合",每个键对应一个值,值可以是任意类型(基础类型、复合类型都可)。TS给对象加类型约束,就是给每个"键"指定对应的"值类型",避免乱赋值。
1. 直接声明:快速约束简单对象
对于结构简单的对象,可直接在变量后标注类型,格式为{ 键1: 类型1; 键2: 类型2; ... },多个键值对用分号分隔(逗号也可,习惯用分号更规范)。
typescript
// 声明一个用户对象类型:name是字符串,age是数字,isAdult是布尔
let user: {
name: string;
age: number;
isAdult: boolean;
} = {
name: "张三",
age: 25,
isAdult: true
};
2. 可选属性:允许"可有可无"的键
实际开发中,有些对象属性不是必填的(比如用户的手机号、邮箱),这时候用?标记为可选属性------赋值时可以省略该属性,也可以补全。
typescript
// 手机号(phone)是可选属性
let user: {
name: string;
age: number;
phone?: string; // 可选属性
} = {
name: "张三",
age: 25
// 省略phone属性,不报错
};
// 也可以补全可选属性
user.phone = "13800138000";
避坑点:访问可选属性时,TS会提示"可能为undefined",需要先判断是否存在再使用,避免运行时错误。
typescript
// 正确写法:先判断phone是否存在
if (user.phone) {
console.log(user.phone.length); // 不报错
}
3. 只读属性:初始化后不能修改的键
有些对象属性初始化后就不能修改(比如用户的ID、订单编号),用readonly标记为只读属性------只能在创建对象时赋值,后续无法修改。
typescript
let user: {
readonly id: number; // 只读属性
name: string;
} = {
id: 1001,
name: "张三"
};
user.name = "张小三"; // 可修改
// user.id = 1002; // 报错:只读属性不能修改
和只读数组类似:只读属性只是"禁止修改属性值",如果属性值是对象(引用类型),对象内部的属性仍可修改。
四、实践:复合类型的嵌套玩法
真实业务场景中,复合类型很少单独使用,大多是"嵌套组合"------比如对象数组、元组嵌套对象、对象里包含数组等。咱们结合两个高频场景,实战演练如何标注类型。
场景1:对象数组(最常用)
比如接口返回的用户列表,是"多个用户对象组成的数组",标注时要先定义单个用户的对象类型,再用数组类型包裹。
typescript
// 单个用户的对象类型
type User = {
id: number;
name: string;
age: number;
phone?: string;
};
// 用户列表:User类型的数组
let userList: User[] = [
{ id: 1001, name: "张三", age: 25 },
{ id: 1002, name: "李四", age: 26, phone: "13800138000" },
{ id: 1003, name: "王五", age: 24 }
];
// 访问嵌套属性:类型安全有提示
console.log(userList[0].name); // 张三(TS自动提示name属性)
场景2:元组嵌套对象
比如处理"用户信息+订单列表"的组合数据,元组第一个元素是用户对象,第二个元素是订单对象数组。
typescript
// 订单对象类型
type Order = {
orderId: string;
amount: number;
};
// 元组:第1个元素是User对象,第2个是Order数组
let userWithOrders: [User, Order[]] = [
{ id: 1001, name: "张三", age: 25 },
[
{ orderId: "OD2024001", amount: 99 },
{ orderId: "OD2024002", amount: 199 }
]
];
// 访问嵌套数据
console.log(userWithOrders[0].name); // 张三
console.log(userWithOrders[1][0].amount); // 99
场景3:对象嵌套数组+可选属性
比如商品信息,包含基础属性和可选的规格数组。
typescript
type Product = {
id: number;
name: string;
price: number;
specs?: string[]; // 可选的规格数组
};
let product: Product = {
id: 2001,
name: "T恤",
price: 99,
specs: ["M码", "L码", "XL码"]
};
// 可选数组的安全访问
if (product.specs) {
console.log(product.specs.length); // 3
}
五、本章小结:复合类型的核心是"结构化约束"
这一章咱们吃透了数组、元组、对象三大复合类型,核心逻辑可以总结为:复合类型是基础类型的"结构化组合",类型标注的本质是给"组合规则"贴标签------数组定"同类元素的队列规则",元组定"固定长度和顺序的队列规则",对象定"键值对的对应规则"。
新手容易踩的坑:一是混淆数组和元组的使用场景(变长用数组,定长用元组);二是忽略可选属性的undefined判断;三是误以为只读复合类型"内部属性也不可修改"。这些都需要通过实践慢慢磨合。
下一章咱们要升级技能,学习函数类型的精准约束------给函数的参数、返回值贴标签,让函数调用更安全、重构更放心。记得多动手写嵌套场景的代码,培养"结构化类型思维",咱们下一章见!