He estado abordando un problema de abstracción de cierta lógica para la creación de componentes con el fin de reducir una gran cantidad de duplicaciones. Como parte de esto, tengo un componente Builder genérico que utilizo para renderizar dinámicamente los componentes en función de los accesorios proporcionados.
El problema proviene del hecho de que definí elementos similares a los siguientes:
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 }Quiero pasar mis campos en un formato similar a este:
const fields = [ { input: "text", props: { onChange: e => console.log(e.target.value) } }, { input: "select", props: { onChange: value => console.log(value) } } ]Este es el tipo que se me ocurrió:
import { ComponentProps } from "react"; export type FieldConfig<T extends FieldValues, K extends keyof InputMap> = { input: K; props?: ComponentProps<InputMap[K]["Component"]> }; Sin embargo, en mi componente Builder , hay un problema al renderizar el componente.
<div> { fields.map(({ input, props }) => { const { Component, controlled } = INPUTS[input] return <Component {...props} /> // ERROR HERE })} </div> const { input, props } = fieldTypeScript en ese punto me da el siguiente error:
Types of property 'onBlur' are incompatible. Type 'ChangeHandler' is not assignable to type '() => void'¿Hay alguna forma de reducir los tipos de una unión a una instancia específica de esa unión en este caso? Estoy haciendo todo lo posible para evitar cualquier tipo de afirmación aquí. ¡Cualquier ayuda sería muy apreciada!
Puede usar una interfaz de campo común y un tipo de unión para definir cómo se debe manejar la estructura de su formulario de esta manera.
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' } } ]Esto permite que se use correctamente al devolver el JSX, aquí hay un enlace a un área de juegos de TypeScript que muestra su uso como componente.
Esto tiene la ventaja adicional de permitirle definir propiedades específicas de tipo específico que se pueden definir como un objeto de options para la entrada select .