一、 协变和逆变

协变(Covariance)\ 逆变(Contravariance)

核心:描述「子类型」和「父类型」在不同场景下的赋值规则,本质是解决「类型兼容」问题。

先定义基础类型(方便后续示例):

1
2
interface Animal { name:string }
interface Dog extends Animal { breed : string}

1.协变: 子->父能赋值(顺理成章)
规则:子类型可以赋值给父类型,常见于【返回值、数组、对象属性】等场景。

1
2
3
4
5
6
7
8
// 示例1:函数返回值(协变)
const getDog = ():Dog => ({name:"旺财",breed:"柴犬"})
const getAnimal = ():Animal => getDog() // ✅ 允许:子类型返回值 → 父类型返回值

// 示例1:数组(协变)
const dogs:Dog[]=[{name:"旺财",breed:"柴犬"}]
const animals:Animal[]=dogs // ✅ 允许:子类型返回值 → 父类型返回值

👉 为什么合理?:拿「动物数组」的场景来说,你要一个 “装动物的数组”,给你 “装狗的数组” 完全没问题(狗也是动物)。

2. 逆变(Contravariance):「父→子」能赋值(反直觉但合理)
✅ 规则:父类型可以赋值给子类型,仅存在于「函数参数」场景。

1
2
3
4
5
6
7
// 示例:函数参数(逆变)
// 接收Animal的函数(父类型参数)
const feedAnimal = (animal: Animal) => console.log(animal.name)
// 接收Dog的函数(子类型参数)
type FeedDog = (dog: Dog) => void

const feedDog: FeedDog = feedAnimal // ✅ 允许:父类型参数函数 → 子类型参数函数

👉 为什么合理?:feedAnimal 只用到了 Animal 的 name 属性,即使传入 Dog(有 name)也能处理;反过来如果用「接收 Dog 的函数」赋值给「接收 Animal 的变量」,会报错(Animal 没有 breed 属性)。

一句话总结:

  • 协变:「更具体的类型」→「更宽泛的类型」(返回值、数组);
  • 逆变:「更宽泛的类型」→「更具体的类型」(仅函数参数)。

二、 infer

核心:infer 是「类型推导关键字」,只能用在 extends 条件类型中,作用是「提取类型的某一部分」,相当于 “类型层面的变量”。

1. 基础用法:提取函数返回值类型

1
2
3
4
5
6
7
8
9
// 定义工具类型:提取函数的返回值类型
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never

// 使用
type Fn1 = () => string
type Fn1Return = GetReturnType<Fn1> // string

type Fn2 = (a: number) => boolean
type Fn2Return = GetReturnType<Fn2> // boolean

👉 逻辑拆解:

  • T extends (...args: any[]) => infer R:判断 T 是否是函数类型;
  • 如果是,用 infer R 推导函数的返回值类型,并把它存到 R 里;
  • 如果不是,返回 never(表示 “不存在的类型”)

2. 进阶用法:提取数组元素类型

1
2
3
4
5
6
7
8
9
// 提取数组的元素类型
type GetArrayItem<T> = T extends (infer Item)[] ? Item : never

type NumArray = number[]
type ItemType = GetArrayItem<NumArray> // number

type StrArray = string[]
type ItemType2 = GetArrayItem<StrArray> // string

核心价值:
不用手动写死类型,动态提取复杂类型的某一部分,是 TypeScript 「类型体操」的核心工具。