Variaciones de sintaxis de componentes de función
Todas las formas de escribir un componente de función de React — declaración vs. expresión, arrow vs. function, export por defecto vs. nombrado, y cuándo importa cada forma.
Receta
Tarjeta de referencia rápida.
// 1. Declaración de función (la más común)
function Greeting({ name }: { name: string }) {
return <h1>Hola, {name}</h1>;
}
// 2. Arrow function asignada a un const
const Greeting = ({ name }: { name: string }) => {
return <h1>Hola, {name}</h1>;
};
// 3. Arrow function con retorno implícito
const Greeting = ({ name }: { name: string }) => (
<h1>Hola, {name}</h1>
);
// 4. Export por defecto — declaración de función
export default function Greeting({ name }: { name: string }) {
return <h1>Hola, {name}</h1>;
}
// 5. Export por defecto — arrow function (en dos pasos)
const Greeting = ({ name }: { name: string }) => (
<h1>Hola, {name}</h1>
);
export default Greeting;
// 6. Export nombrado
export function Greeting({ name }: { name: string }) {
return <h1>Hola, {name}</h1>;
}
// 7. Export nombrado — arrow function
export const Greeting = ({ name }: { name: string }) => (
<h1>Hola, {name}</h1>
);Cuándo recurrir a esto: No necesitas elegir un único estilo de forma exclusiva — pero deberías entender qué te da cada forma y elegir de manera consistente dentro de un proyecto.
Todas las variaciones explicadas
1. Declaración de función
function UserCard({ name, email }: { name: string; email: string }) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
</div>
);
}- Tiene hoisting — puedes llamar a
UserCardpor encima de la línea donde se define - Aparece con nombre en los stack traces — los errores muestran
UserCardpor su nombre - Tiene binding de
this— irrelevante para los componentes, pero significa que puede usarse con.bind()si hiciera falta - La más común en la documentación de React y en las convenciones de Next.js
2. Expresión de arrow function
const UserCard = ({ name, email }: { name: string; email: string }) => {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
</div>
);
};- No tiene hoisting — debe definirse antes de su uso en el mismo archivo
thisléxico — heredathisdel ámbito circundante (irrelevante para los componentes de función, pero importa en los callbacks de componentes de clase)- Aparece con nombre en los stack traces — V8 infiere el nombre a partir del binding del
const - Requiere un punto y coma después de la llave de cierre (es una asignación de variable)
- Popular en bases de código que prefieren
constpara todo
3. Arrow function con retorno implícito
const UserCard = ({ name, email }: { name: string; email: string }) => (
<div>
<h2>{name}</h2>
<p>{email}</p>
</div>
);- Los paréntesis sustituyen a las llaves y a
return— todo el cuerpo es una única expresión - Bueno para componentes simples, solo con JSX y sin lógica antes del retorno
- Se rompe en cuanto añades una variable, un hook o un condicional — debes convertirlo a cuerpo de bloque
4. Export por defecto — declaración de función
export default function Page() {
return <main>Inicio</main>;
}- Convención de Next.js para
page.tsx,layout.tsx,loading.tsx,error.tsx - Muestra el nombre en React DevTools —
Page, noAnonymous - Uno por archivo — solo se permite un export por defecto
5. Export por defecto — arrow function (en dos pasos)
const Page = () => {
return <main>Inicio</main>;
};
export default Page;- Separa la definición del export — útil cuando necesitas adjuntar propiedades estáticas
- Mismo comportamiento en DevTools — React infiere el nombre a partir de la variable
// Adjuntar propiedades estáticas
const Tabs = ({ children }: { children: React.ReactNode }) => {
return <div>{children}</div>;
};
Tabs.Panel = TabPanel;
Tabs.List = TabList;
export default Tabs;6. Export nombrado — declaración de función
export function UserCard({ name }: { name: string }) {
return <h2>{name}</h2>;
}- Se importa con llaves:
import { UserCard } from "./user-card" - Es tree-shakeable — los bundlers pueden descartar los exports nombrados que no se usan
- Varios por archivo — agrupa componentes relacionados juntos
// components/status.tsx — varios componentes relacionados
export function StatusBadge({ status }: { status: string }) {
return <span className="badge">{status}</span>;
}
export function StatusIcon({ status }: { status: string }) {
return <span className="icon">{status === "active" ? "🟢" : "🔴"}</span>;
}7. Export nombrado — arrow function
export const UserCard = ({ name }: { name: string }) => (
<h2>{name}</h2>
);- Los mismos beneficios de tree-shaking que el export de función nombrado
- Algunos equipos lo imponen mediante ESLint para mantener la consistencia con los exports que no son componentes
8. Export por defecto en línea (anónimo)
export default function ({ name }: { name: string }) {
return <h2>{name}</h2>;
}- Sin nombre en DevTools — aparece como
Anonymouso_default - Difícil de depurar — los stack traces carecen de un nombre significativo
- Evita esta forma — nombra siempre tus componentes
9. React.FC / React.FunctionComponent (legado)
const UserCard: React.FC<{ name: string }> = ({ name }) => {
return <h2>{name}</h2>;
};- Incluye
childrende forma implícita en los tipos más antiguos de React (previos a React 18) - React 18+ eliminó los children implícitos de
React.FC— ahora es equivalente al tipado directo - No aporta valor sobre el tipado directo de props — la mayoría de las guías de estilo lo desaconsejan
- Todavía se ve en bases de código legadas — reconócelo pero no lo uses en código nuevo
Tabla comparativa
| Forma | Con hoisting | Con nombre en DevTools | Tree-shakeable | Mejor para |
|---|---|---|---|---|
function Name() {} | Sí | Sí | Solo si se exporta | Uso general, páginas de Next.js |
const Name = () => {} | No | Sí (inferido) | Solo si se exporta | Equipos que prefieren const |
const Name = () => () | No | Sí (inferido) | Solo si se exporta | Componentes diminutos, solo con JSX |
export default function Name() | Sí | Sí | No (por defecto) | Páginas/layouts de Next.js |
export function Name() | Sí | Sí | Sí | Componentes compartidos, utilidades |
export const Name = () => {} | No | Sí (inferido) | Sí | Estilo const consistente |
React.FC<Props> | No | Sí (inferido) | Solo si se exporta | Solo código legado |
¿Cuál deberías elegir?
No hay diferencia funcional entre function y arrow para los componentes. A React le da igual. Elige según las convenciones de tu equipo:
Valores por defecto recomendados:
// Páginas, layouts y rutas de Next.js → export por defecto + declaración de función
export default function DashboardPage() { ... }
// Componentes compartidos → export nombrado + declaración de función
export function UserCard({ name }: UserCardProps) { ... }
// Helpers internos en el mismo archivo → declaración de función simple
function formatStatus(status: string) { ... }Si tu equipo usa arrow functions:
// Páginas de Next.js → sigue usando declaración de función por legibilidad
export default function DashboardPage() { ... }
// Todo lo demás → arrow
export const UserCard = ({ name }: UserCardProps) => { ... };
const formatStatus = (status: string) => { ... };Análisis en profundidad
El hoisting en la práctica
Las declaraciones de función tienen hoisting — están disponibles antes de su posición textual en el archivo. Las arrow functions asignadas a const no.
// Esto funciona — la declaración de función tiene hoisting
export default function Page() {
return <Sidebar />;
}
function Sidebar() {
return <nav>Enlaces</nav>;
}// Esto falla — const NO tiene hoisting
export default function Page() {
return <Sidebar />; // ReferenceError: Cannot access 'Sidebar' before initialization
}
const Sidebar = () => <nav>Enlaces</nav>;En la práctica, esto rara vez importa porque la mayoría de los componentes están en archivos separados. Importa cuando tienes componentes helper debajo del export principal en el mismo archivo.
Componentes genéricos
Las declaraciones de función y las arrow functions manejan los genéricos de forma diferente en TSX:
// Declaración de función — sintaxis limpia
function List<T>({ items, render }: { items: T[]; render: (item: T) => React.ReactNode }) {
return <ul>{items.map((item, i) => <li key={i}>{render(item)}</li>)}</ul>;
}
// Arrow function — necesita una coma final para diferenciarse del JSX
const List = <T,>({ items, render }: { items: T[]; render: (item: T) => React.ReactNode }) => {
return <ul>{items.map((item, i) => <li key={i}>{render(item)}</li>)}</ul>;
};El <T,> con una coma final es necesario porque <T> en un archivo .tsx se interpreta como una etiqueta JSX. La coma le indica a TypeScript que es un genérico. Este es un caso en el que las declaraciones de función tienen una clara ventaja sintáctica.
displayName para depuración
Los componentes de orden superior y React.memo pueden perder el nombre del componente. Establece displayName explícitamente:
const UserCard = React.memo(({ name }: { name: string }) => (
<h2>{name}</h2>
));
UserCard.displayName = "UserCard";
// Con una declaración de función, el nombre se preserva automáticamente
const UserCard = React.memo(function UserCard({ name }: { name: string }) {
return <h2>{name}</h2>;
});
// displayName es automáticamente "UserCard" — no hace falta asignarlo manualmenteVariaciones de forwardRef
// Expresión de función dentro de forwardRef
const Input = React.forwardRef<HTMLInputElement, InputProps>(
function Input({ label, ...props }, ref) {
return (
<label>
{label}
<input ref={ref} {...props} />
</label>
);
}
);
// Arrow function dentro de forwardRef
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ label, ...props }, ref) => (
<label>
{label}
<input ref={ref} {...props} />
</label>
)
);
// React 19: ref es una prop normal — no hace falta forwardRef
function Input({ label, ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
return (
<label>
{label}
<input ref={ref} {...props} />
</label>
);
}Server Components asíncronos
Los Server Components pueden ser async — una sintaxis que solo funciona con declaraciones de función y arrow functions con cuerpo de bloque:
// Declaración de función — limpia
export default async function PostsPage() {
const posts = await getPosts();
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}
// Arrow function — también funciona
const PostsPage = async () => {
const posts = await getPosts();
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
};
export default PostsPage;El retorno implícito (=> (...)) no puede ser async porque await es una sentencia, no una expresión.
Trampas
-
Los exports por defecto anónimos pierden su nombre en DevTools y en los stack traces.
export default function() {}aparece comoAnonymous. Nombra siempre la función, incluso en los exports por defecto. -
Las arrow functions necesitan
<T,>para los genéricos en archivos.tsx. Sin la coma final,<T>se interpreta como un elemento JSX. Las declaraciones de función no tienen esta ambigüedad. -
Los componentes con
constno tienen hoisting. Si defines componentes helper debajo del export principal en el mismo archivo, usa declaraciones de función o mueve los helpers por encima del componente principal. -
React.FCincluíachildrende forma implícita antes de React 18. Si estás migrando desde React 17, eliminarReact.FCpuede sacar a la luz errores de tipo donde se usabachildrenpero no se tipaba explícitamente. -
El retorno implícito se rompe cuando añades lógica.
const Card = () => (<div>...</div>)debe reescribirse comoconst Card = () => { const x = ...; return <div>...</div>; }cuando añades cualquier sentencia antes del JSX. -
export defaultcombinado conconstes un proceso de dos pasos. No puedes escribirexport default const Name = ...— es un error de sintaxis. Usaexport default function Name()o declara primero el const y luegoexport default Name. -
Las reglas de ESLint pueden entrar en conflicto.
react/function-component-definitionimpone un estilo.prefer-arrow-callbackpuede entrar en conflicto. Alinea tu configuración de ESLint con la convención elegida por tu equipo para evitar cambios innecesarios. -
Mezclar estilos en un mismo archivo confunde a los colaboradores. Si
UserCardes una declaración de función yAvatar, justo debajo, es una arrow function, quien lo lea pierde tiempo preguntándose si la diferencia es intencionada. Sé consistente dentro de un archivo.
Alternativas
| Alternativa | Cuándo usarla |
|---|---|
| Componentes de clase | Bases de código legadas, error boundaries (hasta useErrorBoundary de React 19) |
Server Components (async function) | Obtención de datos en el momento del render sin JS de cliente |
| Componentes de orden superior (HOC) | Preocupaciones transversales (auth, logging) — menos comunes ahora con los hooks |
| Render props | Composición dinámica de comportamiento — mayormente reemplazada por los hooks |
Relacionado
- Componentes — patrones de composición, props, children
- JSX y TSX — la sintaxis que devuelven los componentes
- Genéricos de TypeScript — tipado de componentes genéricos en profundidad
- ref como prop en React 19 — forwardRef ya no es necesario