对使用另一个对象键和值类型的对象使用 TS 泛型

问题描述 投票:0回答:1

我在打字稿中使用泛型时遇到一些问题。

我想使用泛型基于另一种对象类型创建一个对象。 我的出发点是 来自 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
};

目前的成果

如果我尝试在对象中使用此 ClickableItem 类型,我会得到所有可能类型值的

render
并集的参数类型,例如:


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

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

渲染方法被错误地推断为键入

string | number
,即使键
a
专门设置为键入
string

在前面的示例中,方法 render 的类型应该是

(value: string)=> string
而不是推断类型
(value: number | string)=> string

这是 ts Playground 链接,包括 TS 文档中的原始示例和我之前提到的测试实现

typescript generics types typescript-typings typescript-generics
1个回答
0
投票
{
  label: 'x',
  key: 'a', 
  render: (value) => value
}

不是

ClickableItem<Example, keyof Example>
的子类型,所以它不会在这里
satisfy


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

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

换句话说,您的代码与:

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

请注意

key
render
函数之间没有任何联系。这实际上已经丢失了,因为您将
keyof Example
作为
K
传入,因此您得到了一种适用于 T
all

键的对象类型

相反,您需要一个联盟,每个属性都有一名成员。

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'>

此映射类型为

T
的每个属性创建一个并集,其中
key
render
属性可以一次强配对一个。

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

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

这是因为该对象与联合体中的一个成员匹配,其中

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];

此类型映射所请求的任何键的并集,并为每个键生成并集。

所以现在:

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>

看游乐场

© www.soinside.com 2019 - 2024. All rights reserved.