我有一个对象映射函数和一个接口(具有与对象相同的键)映射所述函数的参数类型.现在我试着编写一个函数,根据键的不同,可以调用任意一个函数.以下是问题的简化版本:

interface OneFProps {
  a: string;
  b: string;
}

const oneF = (props: OneFProps) => {
  const { a, b } = props;
  return `oneF ${a} ${b}`;
};

interface TwoFProps {
  c: string;
  d: string;
}

const twoF = (props: TwoFProps) => {
  const { c, d } = props;
  return `oneF ${c} ${d}`;
};

const funcMap = {
  oneF: oneF,
  twoF: twoF,
};

interface typeMap {
  oneF: OneFProps;
  twoF: TwoFProps;
}

type tags = keyof typeMap;

type BlendedProps<T extends tags> = {
  key: T;
  z?: string;
  x?: string;
} & typeMap[T];

const blended = <T extends tags>(props: BlendedProps<T>) => {
  const { key, z, x, ...rest } = props;
  const func = funcMap[key];

  return func(rest as any);
};

console.log(blended<'oneF'>({ key: 'oneF', a: 'AAA', b: 'bbb' }));
console.log(blended<'twoF'>({ key: 'twoF', c: 'ccc', d: 'DDD' }));

我不得不写rest as any,因为我无法让 typewriter 工作.我做了一些研究,发现了一些关于分布条件类型的东西,但我不知道这是否真的是这里的问题,也无法找到解决方法.这里有没有办法保持类型安全,或者我的方法在概念上是错误的?

推荐答案

这里的根本问题是编译器无法在blended()的实现中看到func类型和rest类型之间的correlation.这种情况本质上是microsoft/TypeScript#30581的主题,建议的处理方法在microsoft/TypeScript#47109中详细介绍.

如果希望编译器验证funcMap[key]是否接受rest类型的参数,则需要将funcMap的类型显式表示为Tags并集上的mapped type,其中每个属性都接受编译器为rest推断的类型的单个参数.如果查看blendedrest的类型是Omit<BlendedProps<T>, "key" | "z" | "x">.因此,可以像下面这样annotate funcMap:

const funcMap: { [T in Tags]: 
  (props: Omit<BlendedProps<T>, "key" | "z" | "x">) => string 
} = {
    oneF: oneF,
    twoF: twoF,
};

这并没有真正改变funcMap的类型;实际上,编译器可以看到{ oneF: (props: OneFProps) => string; twoF: (props: TwoFProps) => string; }类型的初始值设定项值可分配给{ [T in Tags]: (props: Omit<BlendedProps<T>, "key" | "z" | "x">) => string }类型的变量,因此它们是兼容类型.

但表示方式的差异很重要,因为现在blended()种类型判断的实现:

const blended = <T extends Tags>(props: BlendedProps<T>) => {
    const { key, z, x, ...rest } = props;
    const func = funcMap[key];
    return func(rest); // okay
};

实际上,现在funcMap[key]的类型被推断为(props: Omit<BlendedProps<T>, "key" | "z" | "x">) => string,并且该参数类型与rest的类型相同,因此根据需要接受funcMap[key](rest).

Playground link to code

Typescript相关问答推荐

找不到带名称的管道''

如何使用泛型类型访问对象

为什么从数组中删除一个值会删除打字错误?

webpack错误:模块找不到.当使用我的其他库时,

如何设置绝对路径和相对路径的类型?

具有动态键的泛型类型

在映射类型中用作索引时,TypeScrip泛型类型参数无法正常工作

学习Angular :无法读取未定义的属性(正在读取推送)

了解类型规则中的关键字

TS2339:类型{}上不存在属性消息

Angular material 无法显示通过API获取的数据

在打字应用程序中使用Zod和Reaction-Hook-Forms时显示中断打字时出错

如何静态键入返回数组1到n的函数

字幕子集一个元组的多个元素

作为参数的KeyOf变量的类型

使用NextJS中间件重定向,特定页面除外

如何键入';v型';

数据库中的表请求返回如下日期:2023-07-20T21:39:00.000Z 我需要将此数据格式化为 dd/MM/yyyy HH:mm

在 next.js 13.4 中为react-email-editor使用forwardRef和next/dynamic的正确方法

Typescript 编译错误:TS2339:类型string上不存在属性props