Skip to content

泛型约束与高级推导

核心问题解析

问题一:Vue 的 ref 为什么能自动解包?

在 Vue 3 中:

ts
import { ref } from 'vue';

const count = ref(1);        // Ref<number>
const user = ref({ name: 'Alice' }); // Ref<{ name: string }>

// 在模板或 computed 中可以直接使用:
count.value; // number
user.name;   // ❌ 错误!应该是 user.value.name

但更神奇的是深层 ref 解包(自动 unwrap):

ts
const state = ref({
  count: ref(1),
  name: 'Bob'
});

// 使用时:
state.value.count; // ✅ number,不是 Ref<number>!

答案:Vue 使用了条件类型 + 递归 + 分布式条件类型,在类型层面实现了“如果是一个 ref,则解包其值类型”。

问题二:React 的 useState 怎么推导初始值类型?

ts
const [count, setCount] = useState(0);        // count: number
const [name, setName] = useState('hello');   // name: string
const [user, setUser] = useState<User | null>(null); // user: User | null

useState 如何知道 0number,而 nullUser | null

答案泛型参数从实参推导(Type Inference),并结合联合类型处理 null 等特殊情况。

问题三:如何实现类型级别的“逻辑判断”?

我们希望在类型系统中实现类似 if-else 的逻辑:

text
// 伪代码
if T is array:
  return item type
else if T is function:
  return return type
else:
  return T

答案:使用条件类型T extends U ? X : Y)和 infer 进行类型提取。

学习目标详解

目标一:掌握 infer 在类型提取中的应用

infer 用于在 extends 条件中声明一个待推断的类型变量

1. 提取函数返回值类型

ts
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type T1 = ReturnType<() => string>;        // string
type T2 = ReturnType<(x: number) => number>; // number

2. 提取数组元素类型

ts
type ElementType<T> = T extends (infer U)[] ? U : never;

type T3 = ElementType<number[]>;     // number
type T4 = ElementType<string[][]>;   // string[]

3. 提取 Promise 解包类型

ts
type Awaited<T> = T extends Promise<infer U> ? U : T;

type T5 = Awaited<Promise<string>>;  // string
type T6 = Awaited<number>;           // number

Awaited 实际是递归的,能处理 Promise<Promise<T>>

目标二:实现类型级别的“模式匹配”

通过组合 extendsinfer 和联合类型,实现类型分支逻辑。

示例:自动解包 Ref 类型(模拟 Vue Ref)

ts
// 假设 Ref 定义如下
interface Ref<T> {
  value: T;
}

// 类型级别的“模式匹配”:如果是 Ref<T>,则返回 T,否则保持原类型
type UnwrapRef<T> = T extends Ref<infer U> ? U : T;

type T7 = UnwrapRef<Ref<string>>;    // string
type T8 = UnwrapRef<number>;         // number

深层解包(递归 + 分布式)

ts
// 更复杂的 UnwrapRef,支持对象属性中的 Ref 自动解包
type DeepUnwrapRef<T> = T extends Ref<infer U>
  ? DeepUnwrapRef<U>  // 如果是 Ref,递归解包其值
  : T extends object
    ? { [K in keyof T]: DeepUnwrapRef<T[K]> } // 如果是对象,递归解包每个属性
    : T; // 基本类型直接返回

// 测试
interface State {
  count: Ref<number>;
  profile: {
    name: string;
    age: Ref<number>;
  };
}

type Unwrapped = DeepUnwrapRef<State>;
// {
//   count: number;
//   profile: { name: string; age: number };
// }

这就是 Vue 3 unrefshallowUnwrap 等工具类型的实现原理。

目标三:理解分布式条件类型与延迟推导

1. 分布式条件类型(Distributive Conditional Types)

T联合类型时,T extends U ? X : Y 会自动分发到每个成员:

ts
type ToArray<T> = T extends any ? T[] : never;

type T9 = ToArray<string | number>;
// 等价于:
// (string extends any ? string[] : never) | (number extends any ? number[] : never)
// string[] | number[]

注意:只有“裸类型参数”(naked type parameter)才会分布式。

2. 非分布式(包裹后不分发)

ts
type ToArrayWrapped<T> = [T] extends [any] ? T[] : never;

type T10 = ToArrayWrapped<string | number>;
// [string | number] extends [any] → true → (string | number)[]
// 结果:(string | number)[]

3. 延迟推导(Defer Inference)

有时我们希望推迟 infer 的解析,直到所有信息都可用。

ts
// 用于等待类型完全解析
type Deferred<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;

在复杂递归类型中,延迟推导可避免过早求值。

目标四:分析主流框架中的泛型设计

1. Vue 3:Ref<T>ComputedRef

ts
// 简化版 Ref 定义
interface Ref<T> {
  value: T;
}

// 自动解包逻辑(简化)
type UnwrapRefSimple<T> = T extends Ref<infer U> ? U : T;

// 在 reactive 对象中自动解包
declare function reactive<T>(obj: T): {
  [K in keyof T]: UnwrapRefSimple<T[K]>
};

2. React:useState<T>(initial: T): [T, SetState<T>]

text
function useState<S>(initialState: S | (() => S)): [S, (s: S | ((prev: S) => S)) => void];

// TS 能从 initialState 推导出 S
const [count, setCount] = useState(0); // S 推导为 number

3. Redux Toolkit:createSelector

ts
// 多个输入 selector,一个结果 selector
const selectTotal = createSelector(
  [selectItems, selectTax],
  (items, tax) => items.reduce(/*...*/) * (1 + tax)
);
// 类型系统能推导 items 和 tax 的类型,并检查返回值

使用了泛型元组映射类型来保证类型安全。

总结:本节核心要点

技术用途示例
infer提取类型片段infer R 提取返回值
条件类型类型分支(if-else)T extends U ? X : Y
分布式条件类型联合类型自动分发string | number → string[] | number[]
递归类型处理嵌套结构DeepUnwrapRef
模式匹配类型级别逻辑判断模拟 Vue Ref 解包

高级类型 = 函数式编程 in 类型系统
你正在用 extendsinfer? : 等操作符,在编译期“计算”出新的类型。

练习题

练习题 1:实现 Parameters<T>

提取函数参数类型为元组:

ts
type Parameters<T> = T extends (...args: infer P) => any ? P : never;

type T1 = Parameters<(a: string, b: number) => void>; 
// [string, number]

练习题 2:实现 NonNullable<T>

去除 nullundefined

text
type NonNullable<T> = T extends null | undefined ? never : T;

type T2 = NonNullable<string \| null \| undefined>; // string

练习题 3:模拟 Vue 的 unref

实现一个类型,如果是 Ref<T> 则返回 T,否则返回原类型:

ts
interface Ref<T> {
  value: T;
}

type UnwrapRef<T> = T extends Ref<infer U> ? U : T;

type T3 = UnwrapRef<Ref<number>>;  // number
type T4 = UnwrapRef<string>;       // string

练习题 4:分布式条件类型

解释以下类型的结果:

ts
type Box<T> = T extends string ? `boxed-${T}` : { wrapped: T };

type T5 = Box<'a' | 'b' | number>;
// 分布式:
// 'a' → `boxed-a`
// 'b' → `boxed-b`
// number → { wrapped: number }
// 结果:`boxed-a` | `boxed-b` | { wrapped: number }

练习题 5:深层解包对象中的 Ref

扩展 UnwrapRef,使其能解包对象属性中的 Ref

ts
type DeepUnwrapRef<T> = T extends Ref<infer U>
  ? DeepUnwrapRef<U>
  : T extends object
    ? { [K in keyof T]: DeepUnwrapRef<T[K]> }
    : T;

interface State {
  name: Ref<string>;
  age: number;
  meta: {
    tags: Ref<string[]>;
  };
}

type T6 = DeepUnwrapRef<State>;
// {
//   name: string;
//   age: number;
//   meta: { tags: string[] }
// }

通过本节学习,你已经掌握了 TypeScript 类型系统的“元编程”能力。
infer、条件类型、分布式、递归等特性,让你能在类型层面实现复杂的逻辑判断和结构转换,这正是 Vue、React、Redux 等框架类型安全的基石。