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

TypeScript映射类型疑问:如何将接口值类型用作函数参数类型?

TypeScript映射类型函数参数兼容性问题解决

问题代码与报错

先看这段代码,目标是给Test的每个属性生成对应的校验函数:

type validTypes = string | number

type item = Record<string, validTypes>

interface Test extends item {
  someint: number;
  something: string;
}

let test: {
  [P in keyof Test]: (arg: Test[P]) => boolean
} = {
  someint: (arg) => arg > 0,
  something: (arg) => arg == "hi"  
}

执行时报错:

Type '{ someint: (arg: number) => boolean; something: (arg: string) => boolean; }' is not assignable to type '{ [x: string]: (arg: validTypes) => boolean; someint: (arg: number) => boolean; something: (arg: string) => boolean; }'.
  Property 'someint' is incompatible with index signature.
    Type '(arg: number) => boolean' is not assignable to type '(arg: validTypes) => boolean'.
      Types of parameters 'arg' and 'arg' are incompatible.
        Type 'validTypes' is not assignable to type 'number'.
          Type 'string' is not assignable to type 'number'.

奇怪的是,要是映射类型改成[P in keyof Test]: Test[P]就完全正常,只有把Test[P]用作函数参数时才会出问题。

问题根源

核心原因是函数参数的逆变特性,加上Test继承了Record<string, validTypes>带来的索引签名:

  1. 因为Test extends item,所以keyof Test不仅包含"someint"、"something"这两个字面量键,还包含了string类型的索引签名(对应Record<string, validTypes>里的任意字符串键)。
  2. 映射类型会为这个string索引生成[x: string]: (arg: validTypes) => boolean的约束,要求所有字符串键对应的函数都能接受validTypes(即string | number)类型的参数。
  3. 但someint对应的函数是(arg: number) => boolean,它只能接受number类型,无法处理string类型参数——这违反了函数参数的逆变规则:只有参数类型更宽泛的函数,才能兼容参数类型更窄的函数(比如(arg: validTypes) => boolean可以赋值给(arg: number) => boolean,反过来不行)。
  4. 而不带函数的映射类型没问题,是因为值类型是协变的:number是validTypes的子类型,所以可以直接兼容。

解决方案

方案1:移除不必要的继承

如果你的Test接口不需要支持任意字符串键的validTypes属性,直接去掉extends item即可:

type validTypes = string | number

interface Test {
  someint: number;
  something: string;
}

let test: {
  [P in keyof Test]: (arg: Test[P]) => boolean
} = {
  someint: (arg) => arg > 0,
  something: (arg) => arg == "hi"  
}

此时keyof Test只有两个字面量键,映射类型不会生成索引签名,自然不存在兼容性问题。

方案2:保留继承但排除索引签名

如果必须保留Test extends item的继承关系,可以用Exclude<keyof Test, string>排除掉索引签名对应的string类型键,只针对具体的字面量键做映射:

type validTypes = string | number

type item = Record<string, validTypes>

interface Test extends item {
  someint: number;
  something: string;
}

let test: {
  [P in Exclude<keyof Test, string>]: (arg: Test[P]) => boolean
} = {
  someint: (arg) => arg > 0,
  something: (arg) => arg == "hi"  
}

这样映射类型只会生成someint和something对应的函数类型约束,不会带索引签名,也就解决了兼容性冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 01:29:55