You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中如何约束Record内对象的genSetKey与fn参数匹配?

解决TypeScript中缓存失效器Record的类型推断问题

问题场景

我们需要实现一个由多个invalidator对象组成的Record,每个invalidator包含fn和genSetKey两个函数,要求genSetKey的参数类型必须和对应fn的参数类型完全匹配,用于缓存失效逻辑。

最初的类型定义

一开始写了这两个类型:

type invalidator<TArgs extends unknown[]> = {
  fn: (...args: TArgs) => any;
  genSetKey: (...args: TArgs) => string;
};

type invalidatorRecord<T extends Record<string, unknown>> = {
  [K in keyof T]: T[K] extends invalidator<infer U> ? invalidator<U> : never;
};

这个定义在显式指定类型时能正确检测参数不匹配的错误,但用工具函数makeInvalidatorRecord自动推断类型时,每个invalidator会被推断成never类型,抛出“类型无法赋值给never”的错误,哪怕对象结构完全符合invalidator的定义。

解决方案

问题出在invalidatorRecord的类型约束和推断逻辑上,我们需要调整工具函数的泛型定义,让TypeScript能正确捕获每个invalidator的参数类型:

修正后的类型与工具函数

基础校验版本

type Invalidator<TArgs extends unknown[]> = {
  fn: (...args: TArgs) => any;
  genSetKey: (...args: TArgs) => string;
};

// 直接约束Record值为Invalidator类型
type InvalidatorRecord = Record<string, Invalidator<any[]>>;

// 工具函数通过泛型自动推断并校验
function makeInvalidatorRecord<T extends InvalidatorRecord>(record: T): T {
  return record;
}

精确类型推断版本

如果需要更严格的类型捕获,确保每个键的参数类型被精确推断:

type Invalidator<TArgs extends unknown[]> = {
  fn: (...args: TArgs) => any;
  genSetKey: (...args: TArgs) => string;
};

function makeInvalidatorRecord<
  T extends Record<string, { fn: (...args: any[]) => any; genSetKey: (...args: any[]) => string }>
>(
  record: {
    [K in keyof T]: T[K] extends { fn: (...args: infer U) => any } 
      ? Invalidator<U> 
      : never;
  }
): { [K in keyof T]: Invalidator<T[K] extends { fn: (...args: infer U) => any } ? U : never> } {
  return record as any;
}

验证效果

现在使用makeInvalidatorRecord时,TypeScript会自动推断每个invalidator的参数类型,同时严格校验fn和genSetKey的参数一致性:

// 合法示例:参数类型完全匹配
const validRecord = makeInvalidatorRecord({
  getUser: {
    fn: (id: number) => ({ id, name: "Alice" }),
    genSetKey: (id: number) => `user:${id}`
  },
  getOrder: {
    fn: (orderId: string, userId: number) => ({ orderId, userId }),
    genSetKey: (orderId: string, userId: number) => `order:${orderId}:${userId}`
  }
});

// 非法示例:参数类型不匹配,会触发类型错误
const invalidRecord = makeInvalidatorRecord({
  getUser: {
    fn: (id: number) => ({ id, name: "Alice" }),
    genSetKey: (id: string) => `user:${id}` // 类型错误:参数类型与fn不匹配
  }
});

原理说明

原来的invalidatorRecord因为T extends Record<string, unknown>的约束过于宽泛,TypeScript无法正确提取每个值的invalidator参数类型,导致推断出never。修正后的工具函数通过更精确的泛型约束和条件类型,让TypeScript能逐个捕获每个键对应的fn参数类型,进而约束genSetKey的参数类型,同时保留自动推断的能力。

内容的提问来源于stack exchange,提问作者lentil-soup

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 07:32:05