reactjs - ¿Cómo tipar el forwardRef de React con useImperativeHandle en TypeScript?
Aquí hay una versión simplificada de un componente Tabs que expone un método llamado activateTab a través de useImperativeHandle:
type TabsProps<TabName> = {
tabs: readonly { name: TabName }[];
children: ReactNode;
};
const TabsComponent = <TabName extends string>(
props: TabsProps<TabName>,
ref: Ref<{ activateTab: (tabName: TabName) => void }>
) => {
const { tabs, children } = props;
useImperativeHandle(ref, () => ({
activateTab: (tabName: TabName) => {}
}));
return (
<div>
<div role="tablist">
{tabs.map(({ name }) => (
<button type="button" role="tab" key={name}>
{name}
</button>
))}
</div>
{children}
</div>
);
};
const Tabs = forwardRef(TabsComponent);
// Usage:
const tabs = [{ name: "Tab 1" }, { name: "Tab 2" }] as const;
export default function App() {
return (
<Tabs tabs={tabs}>
{...}
</Tabs>
);
}
Todo está bien hasta este punto. Aquí hay un CodeSandbox que funciona.
Pero, ahora quiero agregar un componente Tabs.Panel para que el uso sea:
export default function App() {
return (
<Tabs tabs={tabs}>
<Tabs.Panel>Content</Tabs.Panel>
</Tabs>
);
}
Intenté lo siguiente, pero TypeScript se queja:
type PanelProps = {
children: ReactNode;
};
const Panel = ({ children }: PanelProps) => {
return <div role="tabpanel">{children}</div>;
};
Tabs.Panel = Panel;
~~~~~
^
Property 'Panel' does not exist on type 'ForwardRefExoticComponent<TabsProps<string> & RefAttributes<{ activateTab: (tabName: string) => void; }>>'
¿Cuál es la mejor manera de lograr esta API Tabs.Panel en TypeScript?
CodeSandbox que no funciona
Respuesta #1
Necesitas usar Object.assign:
import React, { forwardRef, ReactNode, Ref, useImperativeHandle, FC } from "react";
type PanelProps = {
children: ReactNode;
};
const Panel = ({ children }: PanelProps) => {
return <div role="tabpanel">{children}</div>;
};
type TabsProps<TabName> = {
tabs: readonly { name: TabName }[];
children: ReactNode;
};
const TabsComponent = <TabName extends string>(
props: TabsProps<TabName>,
ref: Ref<{ activateTab: (tabName: TabName) => void }>
) => {
const { tabs, children } = props;
useImperativeHandle(ref, () => ({
activateTab: (tabName: TabName) => { }
}));
return (
<div>
<div role="tablist">
{tabs.map(({ name }) => (
<button type="button" role="tab" key={name}>
{name}
</button>
))}
</div>
{children}
</div>
);
};
const Tabs = Object.assign(forwardRef(TabsComponent), { Panel });
const tabs = [{ name: "Tab 1" }, { name: "Tab 2" }] as const;
export default function App() {
return <Tabs tabs={tabs}>
<Tabs.Panel>Content</Tabs.Panel> // ok
</Tabs>;
}
Parque infantil
Aquí puedes encontrar más explicación sobre el uso de propiedades estáticas en funciones
