🔥keyipatience:个人主页
🎬作者简介:C/C++后端开发学习者
🌟专栏传送门:《c++》《linux》《c++高阶数据结构》《c++数据结构与算法》《前端学习》
⭐️patience is key in life
TS 交叉类型&(Intersection Type)
A & B代表这个值必须同时满足 A 和 B 两个类型,拥有两者全部属性,和extends作用很像,但底层逻辑不一样
interface Person { name: string } interface Contact { phone: string } // 交叉类型:合并两个接口 type PersonDetail = Person & Contact // ✅ 必须同时有 name 和 phone,少一个就报错 let obj: PersonDetail = { name: 'jack', phone: '133...' }等价于:
type PersonDetail = { name: string; phone: string }&(交叉) vsextends(接口继承)
共同点:都能合并对象类型
不同点(高频考点):
1.interface B extends A:是接口的继承拓展,同名属性类型冲突 → 直接报错
interface A { x: string } // 报错:x 类型不匹配 interface B extends A { x: number }//接口继承 extends interface A { fn: (value: number) => string } interface B extends A { fn: (value: string) => string } // ❌ 直接报错!2.type B = A & C:是类型交叉合并,同名属性冲突不会立刻红错,而是这个属性变成string & number→ 也就是never(不存在的类型,没法赋值)
type A = { x: string } type B = { x: number } type C = A & B // C里的 x: string & number → 变成 never // 不存在一个值,既是string同时又是number,所以这个属性不可能存在// 交叉类型 & interface A { fn: (value: number) => string } interface B { fn: (value: string) => string } type C = A & B // ✅ 不报错!最终 fn 类型:(value: string | number) => stringTS 泛型
泛型 = 类型的 “占位符 / 变量”,先不固定类型,调用的时候再确定类型,兼顾复用 + 类型安全,解决any丢失类型信息的问题。
方案 1:写死 number(不好)
function id(value: number): number { return value }缺点:只能传数字,传字符串、对象直接报错,没法复用。
方案 2:any(更不好,类型不安全)
function id(value: any): any { return value }- 可以传任意类型
- 丢类型信息:你传
string,返回值 TS 不知道它是 string,它就是any,没有提示、不会校验,等于白写 TS
方案 3:泛型
// <T> 就是泛型,T 是类型占位符(名字随便取,常用T、U) function id<T>(value: T): T { return value }含义: 调用的时候,T会自动变成你传入值的类型,参数类型 = 返回值类型,类型信息保留
id(10) // T = number,返回 number id('hello') // T = string,返回 string id(true) // T = boolean,返回 boolean也可以手动指定类型:
id<string>('abc')对比:
// any版:返回值没有类型 const res1 = id_any("hi") // res1: any // 泛型版:类型跟着进来 const res2 = id("hi") // res2: string泛型简化写法
function id<Type>(value: Type): Type { return value }两种调用写法:
// ✅ 显式指定类型(写死<number>) let num = id<number>(10) // ✅ 省略<类型>,靠TS自动推断(推荐) let num = id(10)1.可以省略<Type>不用手动写<number>,代码更简洁。
2.类型参数推断TS 看你传进去的实参10,自动识别它是number,自动把Type赋值成number。
传
id('abc')→ TS 推断 Type = string
3.什么时候不能省略,必须手写<类型>?当 TS 猜不出来、或者猜出来的类型不是你想要的时候,就要显式传。
function id<T>(v: T): T { return v } // 自动推断:T 是字面量类型 'hi' let a = id('hi') // a 的类型是字面量 'hi',而不是 string // 如果你想要的是宽泛的 string,而不是字面量 'hi',就手动写 let b = id<string>('hi') // 【我想要的类型 > 传入值自带的类型】 const res = id<string | number>('123') // 输入是string,但我主动要求泛型T是 string|number泛型约束(extends)
function id<Type>(value: Type): Type { console.log(value.length) // 报错! return value }原因:Type默认可以是任意类型。
- 如果传字符串
"abc":有.length - 如果传数字
123:number没有.lengthTS 不知道你以后会传什么,为了类型安全,直接不让你访问.length
方式 1:指定更具体的类型(Type[])
function id<Type>(value: Type[]): Type[] { console.log(value.length) return value }参数写成Type[],代表这是一个元素类型为 Type 的数组。 数组天生一定有.length属性,所以 TS 允许访问value.length,不再报错。
id([1,2,3]) // ✅ 合法 id("abc") // ❌ 报错!字符串不是数组方式2 泛型约束Type extends { length: number }
意思:Type 必须是拥有length:number属性的类型(字符串、数组都满足)
interface Lengthwise { length: number } // 约束:Type 必须满足 Lengthwise 的结构 function id<Type extends Lengthwise>(value: Type): Type { console.log(value.length) // ✅ 不再报错 return value }现在调用:
interface Lengthwise { length: number } // 约束:Type 必须满足 Lengthwise 的结构 function id<Type extends Lengthwise>(value: Type): Type { console.log(value.length) // ✅ 不再报错 return value }简写写法(不用单独写 interface)
function id<Type extends { length: number }>(value: Type): Type { console.log(value.length) return value }多泛型 +keyof
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) { return obj[key] } let person = { name: 'jack', age: 18 } getProp(person, 'name')逐段拆解
<Type, Key>:多个泛型变量,逗号隔开Type:代表整个对象的类型Key:代表属性名的类型
keyof Typekeyof是 TS 运算符:拿一个对象类型,提取它所有 key,变成字面量联合类型这里keyof typeof person='name' | 'age'Key extends keyof Type约束:Key必须是这个对象里真实存在的 key
效果演示
getProp(person, 'name') // ✅ 合法 getProp(person, 'age') // ✅ 合法 getProp(person, 'sex') // ❌ 直接红色报错!person没有sex同样number、string、数组本身也都是 TS 里的对象类型,它们也有自己的 key
// 泛型函数:获取对象/原始包装类型上指定key对应的属性值 // Type:传入的数据的类型 // Key extends keyof Type:Key必须是Type类型里存在的键 function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) { // 返回对应key的属性,TS自动推导出返回值类型 return obj[key] } // 1. 普通对象,取age属性 ✅ getProp({ name: 'jack', age: 18 }, 'age') // 2. 普通对象,取name属性 ✅ getProp({ name: 'jack', age: 18 }, 'name') // 3. number类型,toFixed是number自带方法 ✅ getProp(18, 'toFixed') // 4. string类型,split是字符串自带方法 ✅ getProp('abc', 'split') // 5. 字符串支持数字索引取值,'abc'[1] 获取第二个字符 ✅ getProp('abc', 1) // 6. 数组读取length属性 ✅ getProp(['a'], 'length') // 7. 数组支持数字索引;TS只校验类型,不判断下标是否越界 ✅ getProp(['a'], 1000) // 验证:字符串可以用数字下标访问 console.log('object'[1]) // b // ❌ 错误演示:对象上没有 name1 这个key,TS直接报错 // getProp({ name: 'jack', age: 18 }, 'name1')泛型接口
泛型接口 = 给接口套上泛型,让这个接口可以复用,适配多种不同类型,不用重复写很多几乎一样的接口
// 定义泛型接口 IdFunc,Type 是类型变量(占位符) interface IdFunc<Type> { id: (value: Type) => Type ids: () => Type[] } // 使用泛型接口,显式传入 number 替换 Type let obj: IdFunc<number> = { id(value) { return value }, ids() { return [1, 3, 5] } }- 接口名后面加
<类型变量>→ 这个接口就是泛型接口 - 这个类型变量在整个接口内部都能用,接口里所有属性 / 方法都可以引用它
- 使用泛型接口的时候,必须指定具体类型,例子里就是
IdFunc<number> - 代入
number之后:id:接收number,返回numberids:无参,返回number[](数字数组)
TS 里的Array<T>就是内置泛型接口
// 写法等价:string[] === Array<string> const strs = ['a', 'b', 'c'] // TS自动推断 → strs: Array<string> // number[] === Array<number> const nums = [1, 3, 5] // TS自动推断 → nums: Array<number>Array本身是一个泛型接口Array<Type>,Type代表数组元素的类型- TS 会自动类型推导,不用你手动写
Array<string>:- 字符串数组 → 代入
Type = string→Array<string> - 数字数组 → 代入
Type = number→Array<number>
- 字符串数组 → 代入
- 所以调用
.forEach时,回调里的value类型就跟着自动变:Array<string>.forEach:value是stringArray<number>.forEach:value是number
所以两种数组写法是等价的
let arr1: string[] = ['x','y'] let arr2: Array<string> = ['x','y']泛型类
// 定义泛型类 GenericNumber,NumType 是类型变量 class GenericNumber<NumType> { defaultValue: NumType add: (x: NumType, y: NumType) => NumType } // 实例化,指定 NumType = number const myNum = new GenericNumber<number>() myNum.defaultValue = 10class 类名<类型变量>→ 泛型类,和泛型接口写法套路一致- 类型变量
NumType在整个类内部都可用:defaultValue的类型是NumTypeadd接收两个NumType参数,返回NumType
- 实例化的时候
new GenericNumber<number>(),把NumType替换成number- 替换后:
defaultValue: number,add:(x:number,y:number)=>number
- 替换后:
类型能不能自动推导
// 版本1:带构造函数,可以自动推导类型(可以省略 <number>) class GenericNumber<NumType> { defaultValue: NumType add: (x: NumType, y: NumType) => NumType constructor(value: NumType) { this.defaultValue = value } } // 构造函数传了参数 100 → TS 自动推出 NumType = number const myNum = new GenericNumber(100) // ✅ 不用写 <number> // 版本2:没有构造函数,没有参数用来推导类型(推荐手动写 <number>) class GenericNumber<NumType> { defaultValue: NumType add: (x: NumType, y: NumType) => NumType } // new 的时候没有传入任何值,TS没法猜 NumType 是什么类型 const myNum = new GenericNumber<number>() // ✅ 必须显式指定 myNum.defaultValue = 10- 有构造入参:
new GenericNumber(100),实参可以让 TS 自动推导泛型类型,<>可以省略 - 无构造入参:
new GenericNumber(),没有信息用来推导,必须手动写<number>,否则泛型会变成unknown
TS 内置泛型工具类型
全部是 TS 已经写好的泛型,开箱即用,底层都是泛型实现
先准备一个基础接口,后面例子统一用它:
interface User { name: string age: number gender: string }1.Partial<Type>
作用:把Type的所有属性变成可选
type PartialUser = Partial<User> // 等价于:{ name?: string; age?: number; gender?: string } const u: PartialUser = { name: "张三" } // 只写一部分属性也允许2.Readonly<Type>
作用:把Type的所有属性变成只读,不能修改
type ReadonlyUser = Readonly<User> const u: ReadonlyUser = { name: "张三", age: 18, gender: "男" } // u.name = "李四" // ❌ 报错,只读无法修改3.Pick<Type, Keys>
作用:从Type里面挑选指定的几个属性,生成新类型
type PickUser = Pick<User, "name" | "age"> // 等价于:{ name:string; age:number } const u: PickUser = { name:"张三", age:18 }4.Record<Keys, Type>
作用:构造一个对象类型,键是 Keys 里的类型,值是 Type 类型(快速定义键值对字典)
// Record<Keys, Type> type RecordObj = Record<'a' | 'b' | 'c', string[]> let obj: RecordObj = { a: ['1'], b: ['2'], c: ['3'] }等价手写:
type RecordObj = { a: string[] b: string[] c: string[] }约束效果: 只能有a/b/c这三个 key 每一个 key 的值,必须是字符串数组 多写属性、少写属性、值类型写错都会报错
拓展例子:
type Status = "success" | "fail" type MsgObj = Record<Status, string> // { success: string; fail: string } const msg: MsgObj = { success: "操作成功", fail: "操作失败" }索引签名类型
适用场景:不确定对象有多少属性、属性名不固定,允许任意多属性
字符串索引签名
// 字符串索引签名:任意字符串key,值为number interface AnyObject { [key: string]: number } let obj: AnyObject = { a: 1, abc: 124, abcde: 12345 }[key: string]: numberkey:只是占位名字,可以随便改(比如[k:string])string:代表属性名(键)必须是 string 类型number:代表所有属性的值必须是 number
效果:可以随便加任意字符串属性:
obj.c = 99 obj["xxx"] = 100报错:值不是 number 就不行
obj.d = "abc" // 报错数字索引(对应数组)
interface NumIndex { [index: number]: string } const arrLike: NumIndex = ["a", "b"] // 数字索引签名,模拟数组 interface MyArray<Type> { [index: number]: Type } let arr1: MyArray<number> = [1, 3, 5] arr1[0][index: number]: Type
- index 是数字 → 模拟数组下标
arr1[0]、arr1[1]通过数字下标访问元素
和 Record 的区分
- 索引签名:
[k:string]:T→ 键不限制,可以写任意字符串键
type ObjType = { [k:string]: number } //a,b,c,d....都可以- Record<Keys,Type>:键提前限定在联合类型里,只能写指定的几个键
type ObjType = Record<"a"|"b", number> //只能是a,b
- 不知道有哪些 key → 索引签名
- 提前知道只能有哪几个 key → Record