我正试图为我的FormBuilder组件制作类型,但在属性类型提取方面遇到了一些麻烦。
下面是简化后的代码:interface NumericField {
type: 'numeric'
}
interface TextField {
type: 'text'
}
type Field = NumericField | TextField
type Fields = Record<string, Field>
export type PropertyTypeExtractor<T extends Fields> = {
[Property in keyof T]
: T[Property] extends NumericField ? number
: T[Property] extends TextField ? string
: never;
};
const fields = {
numericField: {
type: 'numeric'
},
textField: {
type: 'text'
}
}
const data = {
numericField: 42,
textField: 'lorem ipsum'
}
function createForm<T extends Fields>(fields: T, data: PropertyTypeExtractor<T>) { }
// fields: Type 'string' is not assignable to type '"text"'.
createForm(fields, data)
正如我所看到的,主要问题来自type Fields = Record<string, Field>
, typescript不知道Field
的确切类型。即使type === 'text'
意味着这可能是唯一的TextField
。
Typescript足够聪明:
const anotherFields: Fields = {
numericField: {
type: 'numeric'
},
textField: {
type: 'text'
}
}
const field = anotherFields.numericField // field: Field
if (field.type === 'numeric') {
console.log(field) // field: NumericField
}
那么这里有一些解决我的情况的方法吗?
游乐场
问题是typescript在将您的类型作为参数传递给函数时计算并扩展为类型Fields
。您可以通过以下几种方式解决此问题:
- 将字段直接传递给inline函数:
createForm({
numericField: {
type: 'numeric'
},
textField: {
type: 'text'
}
}, data)
- 使用
as const
来防止字段变量的类型扩展:
const fields2 = {
numericField: {
type: 'numeric'
},
textField: {
type: 'text'
}
} as const;
createForm(fields, data)
- 加入到一个参数类型中,因此推理适用于整个对象。我并不是100%了解为什么这是有效的,但我相信这与同时发生的类型推断有关,而不是在比较两种不同类型时试图找到最合适的:
type FormParameters<T extends Fields> = { fields: T, data: PropertyTypeExtractor<T> };
function createForm<T extends FormParameters<any>>(parameters: T){ }
createForm({ fields, data })