如何使用 TypeScript 中的 Props 数组动态生成 React 组件?
How to generate React components dynamically using an array of Props in TypeScript?
我一直在处理为组件创建抽象出一些逻辑以减少大量重复的问题。作为其中的一部分,我有一个通用的 Builder
组件,用于根据提供的道具动态渲染组件。
问题是因为我将元素定义为类似于以下内容:
type InputMap = typeof INPUTS
const INPUTS = {
text: {
component: TextInput,
controlled: false
},
select: {
component: Select
controlled: true
}
}
// Props for TextInput component
type TextProps = {
onChange: (e: ChangeEvent<HTMLInputElement>) => void
onBlur: (e: ChangeEvent<HTMLInputElement>) => void
}
// Props for Select component
type ElementProps = {
onChange: (value: string) => void
onBlur: () => void
}
我想以类似于此的格式传递我的字段:
const fields = [
{
input: "text",
props: {
onChange: e => console.log(e.target.value)
}
},
{
input: "select",
props: {
onChange: value => console.log(value)
}
}
]
这是我想出的类型:
import { ComponentProps } from "react";
export type FieldConfig<T extends FieldValues, K extends keyof InputMap> = {
input: K;
props?: ComponentProps<InputMap[K]["Component"]>
};
但是在我的 Builder
组件中,渲染组件时出现问题。
<div>
{ fields.map(({ input, props }) => {
const { Component, controlled } = INPUTS[input]
return <Component {...props} /> // ERROR HERE
})}
</div>
const { input, props } = field
此时 TypeScript 出现以下错误:
Types of property 'onBlur' are incompatible.
Type 'ChangeHandler' is not assignable to type '() => void'
在这种情况下,我有什么办法可以将类型从联合缩小到该联合的特定实例吗?我尽力避免在这里进行任何类型断言。任何帮助将不胜感激!
您可以使用公共字段接口和联合类型来定义您的表单结构应该如何处理。
interface FieldDefinition<TType extends string, TElement extends HTMLElement> {
input: TType
placeholder?: string
onChange?: React.ChangeEventHandler<TElement>
onBlur?: React.ChangeEventHandler<TElement>
}
interface TextField extends FieldDefinition<'text', HTMLInputElement> {
}
interface SelectField extends FieldDefinition<'select', HTMLSelectElement> {
options: Record<PropertyKey, any>
}
type FormField = TextField | SelectField
const formFields: FormField[] = [
{
input: 'text',
onChange: (event) => console.log(event.target.value)
},
{
input: 'select',
onChange: (event) => console.log(event.target.value),
options: {
foo: 'Foo',
bar: 'Bar',
baz: 'Baz'
}
}
]
这允许在返回 JSX 时正确使用它,here's a link 到 TypeScript playground 显示它用作组件。
这有额外的好处,允许您定义特定类型的特定属性,这些属性可以定义为 select
输入的 options
对象。
我一直在处理为组件创建抽象出一些逻辑以减少大量重复的问题。作为其中的一部分,我有一个通用的 Builder
组件,用于根据提供的道具动态渲染组件。
问题是因为我将元素定义为类似于以下内容:
type InputMap = typeof INPUTS
const INPUTS = {
text: {
component: TextInput,
controlled: false
},
select: {
component: Select
controlled: true
}
}
// Props for TextInput component
type TextProps = {
onChange: (e: ChangeEvent<HTMLInputElement>) => void
onBlur: (e: ChangeEvent<HTMLInputElement>) => void
}
// Props for Select component
type ElementProps = {
onChange: (value: string) => void
onBlur: () => void
}
我想以类似于此的格式传递我的字段:
const fields = [
{
input: "text",
props: {
onChange: e => console.log(e.target.value)
}
},
{
input: "select",
props: {
onChange: value => console.log(value)
}
}
]
这是我想出的类型:
import { ComponentProps } from "react";
export type FieldConfig<T extends FieldValues, K extends keyof InputMap> = {
input: K;
props?: ComponentProps<InputMap[K]["Component"]>
};
但是在我的 Builder
组件中,渲染组件时出现问题。
<div>
{ fields.map(({ input, props }) => {
const { Component, controlled } = INPUTS[input]
return <Component {...props} /> // ERROR HERE
})}
</div>
const { input, props } = field
此时 TypeScript 出现以下错误:
Types of property 'onBlur' are incompatible.
Type 'ChangeHandler' is not assignable to type '() => void'
在这种情况下,我有什么办法可以将类型从联合缩小到该联合的特定实例吗?我尽力避免在这里进行任何类型断言。任何帮助将不胜感激!
您可以使用公共字段接口和联合类型来定义您的表单结构应该如何处理。
interface FieldDefinition<TType extends string, TElement extends HTMLElement> {
input: TType
placeholder?: string
onChange?: React.ChangeEventHandler<TElement>
onBlur?: React.ChangeEventHandler<TElement>
}
interface TextField extends FieldDefinition<'text', HTMLInputElement> {
}
interface SelectField extends FieldDefinition<'select', HTMLSelectElement> {
options: Record<PropertyKey, any>
}
type FormField = TextField | SelectField
const formFields: FormField[] = [
{
input: 'text',
onChange: (event) => console.log(event.target.value)
},
{
input: 'select',
onChange: (event) => console.log(event.target.value),
options: {
foo: 'Foo',
bar: 'Bar',
baz: 'Baz'
}
}
]
这允许在返回 JSX 时正确使用它,here's a link 到 TypeScript playground 显示它用作组件。
这有额外的好处,允许您定义特定类型的特定属性,这些属性可以定义为 select
输入的 options
对象。