
🔥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 }
&(交叉) vs extends(接口继承)
共同点:都能合并对象类型
不同点(高频考点):
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) => string
TS 泛型
泛型 = 类型的 "占位符 / 变量" ,先不固定类型,调用的时候再确定类型,兼顾复用 + 类型安全,解决 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 = 10
class 类名<类型变量>→ 泛型类,和泛型接口写法套路一致- 类型变量
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