我在Typescript 中使用Generics时遇到了一些问题.

我想使用Generics基于另一个对象类型创建一个对象. 我的起点是this doc from TS

这就是我到目前为止所想到的:

type ClickableItem<T extends Record<string, any>, K extends keyof T> = {
  label: string;
  key: K;
  render?: (value: T[K]) => string;
   // The value (argument) type here should reflect the type from the selected key
};

当前的结果

如果我try 在对象中使用此LinkableProject类型,我将获得所有可能类型值的render个联合的参数类型,例如:


type Example = {
  a: string,
  b: number
}

const item = {
  label: 'x',
  key: 'a',
  render: (value) => value //ERROR HERE
} satisfies ClickableItem<Example, keyof Example>

即使键a被专门设置为类型string,渲染方法也被错误地推断为类型string | number

在之前的示例中,方法渲染的类型应该是(value: string)=> string,而不是推断的类型(value: number | string)=> string.

这里有the ts playground link个,包括TS Doc中的原始示例和我之前提到的测试实现

推荐答案

{
  label: 'x',
  key: 'a', 
  render: (value) => value
}

不是ClickableItem<Example, keyof Example>的亚型,所以这里不会是satisfy.


ClickableItem<Example, keyof Example>扩展到这种类型:

type MyType = {
  label: string;
  key: 'a' | 'b'
  render?: (value: string | number) => string;
};

换句话说,您的代码与:

const itemFromSatisfies = {
  label: 'x',
  key: 'a',
  render: (value) => value //ERROR HERE
} satisfies {
  label: string;
  key: 'a' | 'b'
  render?: (value: string | number) => string;
}

请注意,keyrender函数之间没有任何联系.这实际上是丢失的,因为您将keyof Example传递为K,因此您获得了一个适用于T中的all个键的对象类型.

但您的对象无法处理T中的all个键.它只能处理string,因为render必须返回string,而您只需返回原始值,因此很多都以string开头.


相反,您想要一个每个财产都有一名成员的unions .

type ClickableItemUnion<T extends Record<string, any>> = {
  [K in keyof T]: ClickableItem<T, K>
}[keyof T]


type MyTest = ClickableItemUnion<Example>
// ClickableItem<Example, 'a'> | ClickableItem<Example, 'b'>

此映射类型 for each T的属性创建一个联合,其中keyrender属性可以一次一个强配对.

现在你这样做,它就会起作用:

const itemFromUnion = {
  label: 'x',
  key: 'a', 
  render: (value) => value
} satisfies ClickableItemUnion<Example>

这是因为该对象匹配Union的一个成员,其中key'a',并且render函数接受string.


因此,考虑到所有这些,我们可以通过使用映射类型来简化这个过程,该类型从一开始就产生联合:

type ClickableItem<
  T extends Record<string, any>,
  K extends keyof T
> = {
  [P in K]: {
    label: string;
    key: P;
    render?: (value: T[P]) => string;
  }
}[K];

此类型映射所请求的任何键的联合,并 for each 键生成联合.

所以现在:

type MyType = ClickableItem<Example, keyof Example>

与:

type MyType = {
    label: string;
    key: "a";
    render?: ((value: string) => string) | undefined;
} | {
    label: string;
    key: "b";
    render?: ((value: number) => string) | undefined;
}

因此可以在任何问题上使用satisfies约束:

const item = { // fine
  label: 'x',
  key: 'a', 
  render: (value) => value
} satisfies ClickableItem<Example, keyof Example>

See playground

Typescript相关问答推荐

如何将http上下文附加到angular中翻译模块发送的请求

TypScript在对象上强制执行值类型时推断对象文字类型?

动态判断TypScript对象是否具有不带自定义类型保护的键

处理API响应中的日期对象

angular 17独立使用多个组件(例如两个组件)

JS Redux Devtools扩展不工作

Angular中的其他服务仅获取默认值

将对象属性转换为InstanceType或原样的强类型方法

基于键的值的打字动态类型;种类;

从子类构造函数推断父类泛型类型

在HighChats列时间序列图中,十字准线未按预期工作

如何创建一家Reduxstore ?

Angular 自定义验证控件未获得表单值

基元类型按引用传递的解决方法

使用泛型识别对象值类型

TS2739将接口类型变量赋值给具体变量

如何为特定参数定义子路由?

如何键入并类型的函数&S各属性

在对象类型的类型别名中,属性是用分号 (;) 还是逗号 (,) 分隔的?

基于泛型的柯里化函数的条件参数