我怎样才能让打字稿推断出泛型对象数组中参数的值?
How can I let typescript infer the value of a parameter in an array of generic objects?
我正在创建一个简单的类似模式的类型来使用 React 动态生成表。
这些是我写的类型:
type MySchemaField<T, K extends keyof T> = {
key: K;
label: React.ReactNode;
render?: React.ComponentType<{item: T, value: T[K]}>;
};
type MySchema<T> = {
fields: MySchemaField<T, keyof T>[]; // <-- here is the problem: I don't want to specify the parameter K here because it depends on the single field
};
这就是我期望使用架构的方式:
type MyModel = {
name: string;
slug: string;
location: {address: string, country: string};
};
const schema: MySchema<MyModel> = {
fields: [
{key: 'name', label: 'Name'},
{key: 'slug', label: 'Link', render: ({value}) => value &&
<a href={`/my-path/${value}`} target="_blank" rel="noreferrer">{value}</a> || null}, // value should be inferred as string
{key: 'location', label: 'Address', render: ({value}) => <>{value.country}</>, // value should be {address, country}
],
};
然后我有一些 React 组件接受值 T
和模式 MySchema<T>
。
底层(React)代码工作得很好,但我找不到正确地将 value
作为类型 T[key]
的字段的方法。有没有办法做我想做的事,或者我应该对模式进行不同的建模?
我们需要一种方法让 TypeScript 以某种方式进行关联(知道键与值的类型相关联)。让我们把键的并集变成元组类型,模仿 Object.keys
.
type CreateFields<T, Keys, R extends ReadonlyArray<unknown> = []> =
Keys extends [] // if no keys
? R // then we are done
: Keys extends [infer Key, ...infer Rest]
? CreateFields<T, Rest, [...R, {
key: Key;
label: React.ReactNode;
render?: Key extends keyof T ? React.ComponentType<{ item: T; value: T[Key] }> : never;
}]>
: never;
我们有模式 T
、假定的键 T
、Keys
和结果 R
。
遍历键,为每个键创建一个字段,并为该字段使用正确的值类型。这基本上是映射键并“返回”一个值。
因为这个类型returns是一个元组,我们不能直接使用它。我们必须先得到元组元素的类型,我们可以用 [number]
来做,然后用 []
从中得到一个数组,结果是这个特殊的代码:
type MySchema<T> = {
fields: CreateFields<T, TuplifyUnion<keyof T>>[number][];
};
这似乎在操场上有效
p.s。肯定有更好更高效的方法;这只是我想到的第一个
对于 [=17= 中的每个 K
,您希望 MySchema<T>
的 fields
属性 是 MySchemaField<T, K>
的 union ].也就是说,您想 分布 MySchemaField<T, K>
K
.
中的工会
有多种方法可以做到这一点。我在这里的方法可能是制作一个分布式对象类型(如microsoft/TypeScript#47109), where you make a mapped type and immediately index into it中创造的那样)。像{[K in KS]: F<K>}[KS]
这样的东西最终会成为[=21=的联合] 对于 KS
中的每个 K
。在你的情况下 KS
是 keyof T
,而 F<K>
是 MySchemaField<T, K>
。像这样:
type MySchema<T> = {
fields: Array<{ [K in keyof T]-?: MySchemaField<T, K> }[keyof T]>;
};
让我们看看它是如何工作的:
type MySchemaMyModel = MySchema<MyModel>
/* type MySchemaMyModel = {
fields: (
MySchemaField<MyModel, "name"> |
MySchemaField<MyModel, "slug"> |
MySchemaField<MyModel, "location">
)[];
} */
这就是你想要的,对吧?现在一切都按照您的意愿进行推断:
const schema: MySchema<MyModel> = {
fields: [
{ key: 'name', label: 'Name' },
{
key: 'slug', label: 'Link', render: ({ value }) => value &&
<a href={`/my-path/${value}`} target="_blank" rel="noreferrer">{value}</a>
|| null
},
{ key: 'location', label: 'Address', render: ({ value }) =>
<>{value.country}</>, }
],
};
我正在创建一个简单的类似模式的类型来使用 React 动态生成表。
这些是我写的类型:
type MySchemaField<T, K extends keyof T> = {
key: K;
label: React.ReactNode;
render?: React.ComponentType<{item: T, value: T[K]}>;
};
type MySchema<T> = {
fields: MySchemaField<T, keyof T>[]; // <-- here is the problem: I don't want to specify the parameter K here because it depends on the single field
};
这就是我期望使用架构的方式:
type MyModel = {
name: string;
slug: string;
location: {address: string, country: string};
};
const schema: MySchema<MyModel> = {
fields: [
{key: 'name', label: 'Name'},
{key: 'slug', label: 'Link', render: ({value}) => value &&
<a href={`/my-path/${value}`} target="_blank" rel="noreferrer">{value}</a> || null}, // value should be inferred as string
{key: 'location', label: 'Address', render: ({value}) => <>{value.country}</>, // value should be {address, country}
],
};
然后我有一些 React 组件接受值 T
和模式 MySchema<T>
。
底层(React)代码工作得很好,但我找不到正确地将 value
作为类型 T[key]
的字段的方法。有没有办法做我想做的事,或者我应该对模式进行不同的建模?
我们需要一种方法让 TypeScript 以某种方式进行关联(知道键与值的类型相关联)。让我们把键的并集变成元组类型,模仿 Object.keys
.
type CreateFields<T, Keys, R extends ReadonlyArray<unknown> = []> =
Keys extends [] // if no keys
? R // then we are done
: Keys extends [infer Key, ...infer Rest]
? CreateFields<T, Rest, [...R, {
key: Key;
label: React.ReactNode;
render?: Key extends keyof T ? React.ComponentType<{ item: T; value: T[Key] }> : never;
}]>
: never;
我们有模式 T
、假定的键 T
、Keys
和结果 R
。
遍历键,为每个键创建一个字段,并为该字段使用正确的值类型。这基本上是映射键并“返回”一个值。
因为这个类型returns是一个元组,我们不能直接使用它。我们必须先得到元组元素的类型,我们可以用 [number]
来做,然后用 []
从中得到一个数组,结果是这个特殊的代码:
type MySchema<T> = {
fields: CreateFields<T, TuplifyUnion<keyof T>>[number][];
};
这似乎在操场上有效
p.s。肯定有更好更高效的方法;这只是我想到的第一个
对于 [=17= 中的每个 K
,您希望 MySchema<T>
的 fields
属性 是 MySchemaField<T, K>
的 union ].也就是说,您想 分布 MySchemaField<T, K>
K
.
有多种方法可以做到这一点。我在这里的方法可能是制作一个分布式对象类型(如microsoft/TypeScript#47109), where you make a mapped type and immediately index into it中创造的那样)。像{[K in KS]: F<K>}[KS]
这样的东西最终会成为[=21=的联合] 对于 KS
中的每个 K
。在你的情况下 KS
是 keyof T
,而 F<K>
是 MySchemaField<T, K>
。像这样:
type MySchema<T> = {
fields: Array<{ [K in keyof T]-?: MySchemaField<T, K> }[keyof T]>;
};
让我们看看它是如何工作的:
type MySchemaMyModel = MySchema<MyModel>
/* type MySchemaMyModel = {
fields: (
MySchemaField<MyModel, "name"> |
MySchemaField<MyModel, "slug"> |
MySchemaField<MyModel, "location">
)[];
} */
这就是你想要的,对吧?现在一切都按照您的意愿进行推断:
const schema: MySchema<MyModel> = {
fields: [
{ key: 'name', label: 'Name' },
{
key: 'slug', label: 'Link', render: ({ value }) => value &&
<a href={`/my-path/${value}`} target="_blank" rel="noreferrer">{value}</a>
|| null
},
{ key: 'location', label: 'Address', render: ({ value }) =>
<>{value.country}</>, }
],
};