javascript - Tipos condicionales para un componente React dinámico en Typescript
Intentando crear un componente dinámico de React. ¿Cuál es la forma correcta de pasar condicionalmente el propType correcto según el componente seleccionado? Esto es lo que tengo hasta ahora, estoy obteniendo un error en esta línea <SelectComponent {...props.props} /> porque los props no coinciden con los componentes:
export interface SidebarProps {
type: keyof typeof components,
props: AddEditJobsProps | AddEditCustomersProps
}
const components = {
job: AddEditJobs,
customer: AddEditCustomers
};
export default function Sidebar(props: SidebarProps) {
const { open, toggle } = useToggle();
const SelectComponent = components[props.type];
return (
<RightSidebar open={open} toggleDrawer={toggle}>
<SelectComponent {...props.props} />
</RightSidebar>
);
}
Editar: agregar cualquier a las props corrige el error; sin embargo, Typescript no podrá hacer coincidir el tipo seleccionado con las props correspondientes durante la verificación de tipos, que es lo que espero lograr aquí.
export interface SidebarProps {
type: keyof typeof components,
props: AddEditJobsProps | AddEditCustomersProps | any
}
Respuesta #1
Si insistes en mantenerlo dinámico de esa manera, aquí está cómo probablemente deberías hacerlo.
El problema número uno, como mencioné en mi comentario, es que TS no sabe cuál será el valor de props.type hasta el tiempo de ejecución, por lo que no puede inferir de manera efectiva lo que debería ser de antemano. Para resolver esto, necesitas algo como una condición simple que renderice explícitamente el componente correcto:
export const Sidebar: React.FC<SidebarProps> = props => {
const { open, toggle } = useToggle();
let inner: React.ReactNode;
if (props.type === "job") {
inner = <AddEditJobs {...props.props} />;
} else if (props.type === "customer") {
inner = <AddEditCustomers {...props.props} />;
}
return (
<RightSidebar open={open} toggleDrawer={toggle}>
{inner}
</RightSidebar>
);
};
Tenga en cuenta que el condicional básicamente afirma cuál será el valor de ese campo, lo que ayuda a TS a inferir cuál será el resto de la estructura y mejora la verificación de tipos en tiempo de codificación.
El problema número dos es que tu interfaz para SidebarProps es demasiado permisiva.
Tienes esto:
export interface SidebarProps {
type: keyof typeof components,
props: AddEditJobsProps | AddEditCustomersProps
}
Esto básicamente le está diciendo a Typescript "el objeto debe tener un campo type que coincida con una de las claves en components, y un campo props que sea AddEditJobsProps o AddEditCustomersProps". Pero no está especificando que si type es igual a "job" entonces el campo props debe coincidir con AddEditJobsProps. Para hacer eso necesitas decirlo de manera más explícita:
export type SidebarProps =
| {
type: "job";
props: AddEditJobsProps;
}
| { type: "customer"; props: AddEditCustomersProps };
Esto utiliza tipos de unión para asegurar que SidebarProps tenga una u otra forma completa y válida.
Con estos cambios, no solo desaparece el error de TS en Sidebar, sino que al renderizarlo en otro componente obtendrás la comprobación de TS esperada y correcta. Si type es "job" pero la propiedad props no tiene la forma esperada de AddEditJobsProps, obtendrás un error. Pruébalo tú mismo en este sandbox:
https://codesandbox.io/s/youthful-fog-8lq41
Respuesta #2
Creo que la respuesta es que tu patrón de "componente dinámico" aquí está haciendo las cosas difíciles. Es confuso tanto para humanos como para computadoras entenderlo, resultando en un código que es difícil de leer y mantener, y que el compilador de Typescript no puede verificar con precisión.
Un mejor patrón para usar aquí sería la composición de componentes. Haz que <Sidebar> sea más genérico para que no le importe qué hijos renderiza, y solo maneje el estado de apertura/alternancia.
export default const Sidebar: React.FC = ({children}) => {
const { open, toggle } = useToggle();
return (
<RightSidebar open={open} toggleDrawer={toggle}>
{children}
</RightSidebar>
);
}
Luego, cuando quieras renderizar una barra lateral, simplemente le das los elementos secundarios que quieres que envuelva:
<Sidebar>
<AddEditJobs {...addEditJobsProps} />
</Sidebar>
// or
<Sidebar>
<AddEditCustomers {...addEditCustomersProps} />
</Sidebar>
Obtendrás una verificación de tipos precisa y estricta (porque TS conocerá el tipo exacto de los componentes y las props) y la estructura de tu código será más legible y fácil de seguir.
