我正在try 创建一个Reaction组件,该组件基于Configuration对象呈现输入表,该对象定义了字段属性名称及其输入类型和初始数据行.
以下是预期用途的示例:
const render = <C extends Config>(config: C, initialData: Row<C>[]) => {
return initialData
}
const config = {
properties: [
{
input: InputType.Text,
label: 'Crop',
name: 'crop',
},
{
input: InputType.Number,
label: 'Yield(t)',
name: 'yield_t',
},
],
}
const initialData = [
{ crop: 'corn', yield_t: 2 },
{ crop: 'barley', yield_t: 2 },
{ crop: 2, yield_t: 'ten' }, // ❌ bad input
]
render(config, initialData)
我正在try 得到像上面这样的例子,输入错误,让Typescript 抱怨.
以下是我到目前为止所拥有的(also in a TS playground)
/**
* WIP:
*
* An attempt to create typescript-level type validation of
* initialData dependent on the properties defined in config.
*/
enum InputType {
Text = 'Text',
Number = 'Number',
Date = 'Date',
}
export type FieldConfig = {
input: InputType
label: string
name: string
}
export type Config = {
properties: ReadonlyArray<FieldConfig>
}
export type Row<C extends Config> = {
[K in Names<C>]: GetFieldType<Extract<Properties<C>, { name: K }>['input']>
}
/* Extracts a union of properties from a config type,
* i.e. { name: 'crop', ... } | { name: 'yield_t', ... } */
type Properties<C extends Config> = C['properties'][number]
/* Gets the names of of the properties from a config type,
* i.e. 'crop' | 'yield_t' */
type Names<C extends Config> = C['properties'][number]['name']
/**
* Gets the scalar type of an Input Type
*/
type GetFieldType<Type extends keyof typeof InputType> =
Type extends InputType.Text
? string
: Type extends InputType.Date
? string
: Type extends InputType.Number
? number
: never
/**
* Converts an union of Properties into a mapped object type, i.e.:
*
* properties: [{ name: 'crop', ... }, { name: 'yield_t', ... }]
* ->
* {
* 'crop': { name: 'crop', ... },
* 'yield_t': { name: 'yield_t', ... },
* }
*/
type NameMap<C extends Config> = {
[PropertyName in Names<C>]: Extract<Properties<C>, { name: PropertyName }>
}
const render = <C extends Config>(config: C, initialData: Row<C>[]) => {
return initialData
}
const config = {
properties: [
{
input: InputType.Text,
label: 'Crop',
name: 'crop',
},
{
input: InputType.Number,
label: 'Yield(t)',
name: 'yield_t',
},
],
}
const initialData: Row<typeof config>[] = [
{ crop: 'corn', yield_t: 2 },
{ crop: 'barley', yield_t: 2 },
// @ts-expect-error this should fail :(
{ crop: 2, yield_t: 'ten' },
]
render(
config,
// @ts-expect-error this should fail :(
initialData,
)
最后,我的Row
类型具有通用的字符串键,其属性是可能的输入值的联合:
理想情况下,推断的定义应如下所示:
type MyRow = {
crop: string;
yield_t: number
}