Un RenderFragment es un delegado que representa un fragmento de interfaz que Blazor puede renderizar.
En el artículo anterior pasamos datos de padres a hijos. Si un componente Ventana.razor recibiera su contenido como string, solo podría mostrar texto o tendría que interpretar HTML, con los problemas de seguridad y mantenimiento que eso implica.
Con RenderFragment podemos pasar HTML, componentes y expresiones Razor sin convertirlos en una cadena. El concepto es parecido a los slots de otros frameworks.
El estándar: ChildContent
Blazor aplica una convención: si defines un parámetro de tipo RenderFragment y lo llamas exactamente ChildContent, captura todo lo que pongas entre las etiquetas de apertura y cierre del componente.
Vamos a crear un componente contenedor simple: AlertBox.razor.
Definición del componente (AlertBox.razor)
<div class="alert alert-info">
<h4 class="alert-heading">¡Atención!</h4>
<div class="alert-body">
@ChildContent
</div>
</div>
@code {
// El nombre debe ser EXACTAMENTE ChildContent para que funcione el comportamiento implícito
[Parameter]
public RenderFragment? ChildContent { get; set; }
}Uso desde el padre
Ahora, al usarlo, no necesitamos asignar el atributo ChildContent="...". Simplemente escribimos HTML dentro.
<AlertBox>
<p>Esto es un párrafo <strong>con negritas</strong> pasado como parámetro.</p>
<button class="btn btn-sm btn-danger">Incluso botones funcionales</button>
</AlertBox>El componente AlertBox define el contenedor (borde, color y título), pero delega el contenido interior en el componente padre.
Varios fragmentos con nombre
¿Qué pasa si un solo hueco no es suficiente? Imaginemos un componente Card que necesita un hueco para el Header, otro para el Body y otro para el Footer.
Podemos definir tantos RenderFragment como queramos. La única diferencia es que, al usarlos, tendremos que especificar en qué hueco va cada contenido.
El componente (MyCard.razor):
<div class="card">
<div class="card-header">
@* Si el usuario no pasa Header, podemos poner un default o no mostrar nada *@
@if (Header != null)
{
@Header
}
else
{
<span>Título por defecto</span>
}
</div>
<div class="card-body">
@Body
</div>
<div class="card-footer text-muted">
@Footer
</div>
</div>
@code {
[Parameter] public RenderFragment? Header { get; set; }
[Parameter, EditorRequired] public RenderFragment Body { get; set; } = default!;
[Parameter] public RenderFragment? Footer { get; set; }
}El uso:
Ahora usamos etiquetas XML anidadas que coinciden con los nombres de nuestros parámetros.
<MyCard>
<Header>
<h3>Usuario: Luis</h3>
</Header>
<Body>
<p>Aquí va el contenido principal de la tarjeta.</p>
<img src="avatar.png" alt="Avatar de Luis" />
</Body>
<Footer>
<button>Ver perfil</button>
</Footer>
</MyCard>Fíjate que ya no usamos la propiedad implícita ChildContent, sino etiquetas explícitas <Header>, <Body>, etc. Esto mejora enormemente la legibilidad del código.
Componentes con plantillas genéricas
Hasta ahora, el padre definía el HTML y el hijo decidía dónde renderizarlo. Los componentes genéricos amplían esta idea para trabajar con datos de cualquier tipo.
Existe además un escenario más complejo: los componentes genéricos de listas o tablas.
Pensemos en un componente Listado<T>.
- El componente sabe cómo iterar la lista y maquetar la rejilla.
- Pero el componente no sabe qué propiedades de
Tdebe pintar (porqueTpuede ser unProducto, unUsuarioo unCoche).
Para esto usamos RenderFragment<T>. Esto permite al hijo devolver datos al padre para que el padre decida cómo pintarlos.
El Componente (GenericList.razor):
@typeparam TItem
<div class="lista-bonita">
@foreach (var item in Items)
{
<div class="item-fila">
@ItemTemplate(item)
</div>
}
</div>
@code {
[Parameter]
public IReadOnlyList<TItem> Items { get; set; } = [];
// Este fragmento acepta un parámetro de tipo TItem
[Parameter]
public RenderFragment<TItem> ItemTemplate { get; set; } = default!;
}El uso:
Cuando usamos el componente, definimos la plantilla y recibimos el objeto a través de la variable de contexto (por defecto context).
<h3>Listado de Productos</h3>
<GenericList Items="@misProductos">
<ItemTemplate>
<strong>@context.Nombre</strong> - <span>@context.Precio €</span>
</ItemTemplate>
</GenericList>
<h3>Listado de Usuarios</h3>
<GenericList Items="@misUsuarios">
<ItemTemplate Context="user"> <div class="user-badge">
<img src="@user.Avatar" alt="Avatar de @user.Alias" /> @user.Alias
</div>
</ItemTemplate>
</GenericList>Esta técnica es la base de componentes complejos como DataGrids, Virtualizers o cualquier control de interfaz avanzado que veas en librerías de terceros.