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

限制泛型类型为已知属性:TypeScript create函数参数校验问题

问题

我有一个create泛型函数,它接收Settings类型的可选参数,返回的FuncFact类型结构会随传入的Settings参数变化。当前实现里,传入未知属性(比如thisShouldMakeTSCompilerAngry)时TypeScript不会报错,但我希望能禁止这类未知属性。

当前代码实现如下:

// 所有属性都是可选的
interface Settings {
    name?: string;
    usable?: boolean;
    values?: { [key: string]: string };
}

type FuncFact<S = Settings> = S extends { values?: { [key: string]: string } } 
  ? Record<keyof S['values'], Function> 
  : Record<never, Function>

declare function create<MySettings extends Settings>(settings?: MySettings): FuncFact<MySettings>;

const z = create({
  name: 'John',
  values: {
    up: '+1',
    down: '-1'
  },
  thisShouldMakeTSCompilerAngry: 'dummy' // 这里希望TS编译器报错,只允许Settings的属性
});

z.up();  // 这个功能需要保留正常工作

我知道create必须是泛型函数,不能直接用Settings作为参数类型,因为要根据传入的values结构推导返回类型。我考虑过用keyof MySettings extends keyof Settings来限制,但不确定怎么实现。

解决方案

要实现严格的属性校验,同时保留泛型推导能力,可以通过约束泛型类型与Settings完全匹配来实现。下面提供两种可行的写法:

写法一:直接在泛型约束中限制

// 所有属性都是可选的
interface Settings {
    name?: string;
    usable?: boolean;
    values?: { [key: string]: string };
}

type FuncFact<S = Settings> = S extends { values?: infer V } 
  ? V extends { [key: string]: string } 
    ? Record<keyof V, Function> 
    : Record<never, Function>
  : Record<never, Function>

// 泛型约束:确保MySettings的所有键都属于Settings的键
declare function create<MySettings extends Settings & Record<keyof MySettings, keyof Settings>>(
  settings?: MySettings
): FuncFact<MySettings>;

const z = create({
  name: 'John',
  values: {
    up: '+1',
    down: '-1'
  },
  thisShouldMakeTSCompilerAngry: 'dummy' // TS编译器会报错,符合预期
});

z.up();  // 依然可以正常调用,类型推导正常

解释

  • 泛型约束MySettings extends Settings & Record<keyof MySettings, keyof Settings>的作用是:既让MySettings继承Settings的属性定义,又强制MySettings的所有键都只能是Settings中已存在的键。一旦传入未知键,TypeScript会检测到keyof MySettings包含不属于keyof Settings的字段,直接触发报错。
  • 优化后的FuncFact用infer V推导values的具体类型,让类型逻辑更清晰,同时保留原有功能——根据传入的values结构生成对应的函数记录类型。

写法二:用辅助类型实现Exact匹配

// 所有属性都是可选的
interface Settings {
    name?: string;
    usable?: boolean;
    values?: { [key: string]: string };
}

type FuncFact<S = Settings> = S extends { values?: infer V } 
  ? V extends { [key: string]: string } 
    ? Record<keyof V, Function> 
    : Record<never, Function>
  : Record<never, Function>

// 定义Exact辅助类型,强制两个类型完全匹配
type Exact<T, U> = T extends U ? (U extends T ? T : never) : never;

declare function create<MySettings extends Settings>(
  settings?: Exact<MySettings, Settings>
): FuncFact<MySettings>;

const z = create({
  name: 'John',
  values: {
    up: '+1',
    down: '-1'
  },
  thisShouldMakeTSCompilerAngry: 'dummy' // TS编译器会报错,符合预期
});

z.up();  // 功能正常保留

解释

Exact<T, U>类型的作用是仅当T和U的结构完全一致时才返回T,否则返回never。把它作为参数类型约束后,传入未知属性时,Exact<MySettings, Settings>会变成never,触发编译器报错,同时不影响正常的泛型推导逻辑。

内容的提问来源于stack exchange,提问作者UndefinedBehavior

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 21:47:10