reactjs - Inferir el tipo de props basado en el componente pasado también como prop
¿Es posible inferir tipos correctos para las props de un componente desconocido pasado también como prop?
En caso de un componente conocido (que existe en el archivo actual) puedo obtener props:
type ButtonProps = React.ComponentProps<typeof Button>;
Pero si quiero crear un componente genérico Box que acepte un componente en la propiedad as y las propiedades del componente en la propiedad props. El componente puede agregar algunas propiedades predeterminadas, tener algún comportamiento, no importa. Básicamente es similar a los componentes de orden superior, pero es dinámico.
import React from "react";
export interface BoxProps<TComponent> {
as?: TComponent;
props?: SomehowInfer<TComponent>; // is it possible?
}
export function Box({ as: Component, props }: BoxProps) {
// Note: it doesn't have to be typed within the Box (I can pass anything, I can control it)
return <Component className="box" title="This is Box!" {...props} />;
}
function MyButton(props: {onClick: () => void}) {
return <button className="my-button" {...props} />;
}
// usage:
function Example() {
// I want here the props to be typed based on what I pass to as. Without using typeof or explicitly passing the generic type.
return (
<div>
<Box
as={MyButton}
props={{
onClick: () => {
console.log("clicked");
}
}}
>
Click me.
</Box>
</div>
);
}
requisitos :
- debe funcionar sin pasar el tipo genérico (¿es posible?), porque se usaría casi en todas partes
- debe trabajar con componentes definidos por el usuario (
React.ComponentType<Props>) - sería genial si también funcionara con elementos HTML de react (a, button, link, ...ellos tienen diferentes props), pero no es necesario
Respuesta #1
Puedes usar el tipo de React predefinido ComponentProps para extraer los tipos de props de un tipo de componente.
import React from "react";
export type BoxProps<TComponent extends React.ComponentType<any>> = {
as: TComponent;
props: React.ComponentProps<TComponent>;
}
export function Box<TComponent extends React.ComponentType<any>>({ as: Component, props }: BoxProps<TComponent>) {
return <div className="box" title="This is Box!">
<Component {...props} />;
</div>
}
function MyButton(props: {onClick: () => void}) {
return <button className="my-button" {...props} />;
}
// usage:
function Example() {
// I want here the props to be typed based on what I pass to as. Without using typeof or explicitly passing the generic type.
return (
<div>
<Box
as={MyButton}
props={{ onClick: () => { } }}
></Box>
</div>
);
}
Enlace del parque
Dependiendo de tu caso de uso exacto, la solución podría variar, pero la idea básica es similar. Podrías, por ejemplo, darle un giro al tipo y tomar las props como el parámetro de tipo para BoxProps. De esa manera puedes restringir las props del componente para que tengan algunas propiedades específicas que puedes suministrar dentro del componente Box:
export type BoxProps<TProps extends {title: string}> = {
as: React.ComponentType<TProps>;
} & {
props: Omit<TProps, 'title'>;
}
export function Box<TProps extends {title: string}>({ as: Component, props }: BoxProps<TProps>) {
return <div className="box" title="This is Box!">
<Component title="Title from box" {...props as TProps} />;
</div>
}
Enlace del parque
Si quieres incluir etiquetas intrínsecas, también puedes agregar keyof JSX.IntrinsicElements a la restricción de TComponent:
export type BoxProps<TComponent extends React.ComponentType<any> | keyof JSX.IntrinsicElements> = {
as: TComponent;
props: React.ComponentProps<TComponent>;
}
export function Box<TComponent extends React.ComponentType<any>| keyof JSX.IntrinsicElements>({ as: Component, props }: BoxProps<TComponent>) {
return <div className="box" title="This is Box!">
<Component {...props} />;
</div>
}
Enlace del parque
