Componentes con plantilla de Blazor en ASP.NET Core
Los componentes con plantilla son componentes que aceptan una o varias plantillas de interfaz de usuario como parámetros, que se pueden usar como parte de la lógica de representación del componente. Los componentes con plantilla permiten crear componentes de nivel superior que son más reutilizables que los componentes normales. Estos son un par de ejemplos:
- Un componente de tabla que permite a un usuario especificar plantillas para el encabezado, las filas y el pie de página de la tabla.
- Un componente de lista que permite a un usuario especificar una plantilla para representar elementos en una lista.
Un componente con plantilla se define especificando uno o más parámetros de componente de tipo RenderFragment o RenderFragment<TValue>. Un fragmento de representación representa un segmento de interfaz de usuario que se va a representar. RenderFragment<TValue> toma un parámetro de tipo que se puede especificar cuando se invoca el fragmento de representación.
A menudo, los componentes con plantilla tienen un tipo genérico, como se muestra en el siguiente componente de TableTemplate. El tipo genérico <T> en este ejemplo se usa para representar valores de IReadOnlyList<T>, que en este caso es una serie de filas de mascotas en un componente que muestra una tabla de mascotas.
Shared/TableTemplate.razor:
@typeparam TItem
@using System.Diagnostics.CodeAnalysis
<table class="table">
<thead>
<tr>@TableHeader</tr>
</thead>
<tbody>
@foreach (var item in Items)
{
if (RowTemplate is not null)
{
<tr>@RowTemplate(item)</tr>
}
}
</tbody>
</table>
@code {
[Parameter]
public RenderFragment? TableHeader { get; set; }
[Parameter]
public RenderFragment<TItem>? RowTemplate { get; set; }
[Parameter, AllowNull]
public IReadOnlyList<TItem> Items { get; set; }
}
Al usar un componente con plantilla, los parámetros de plantilla se pueden especificar utilizando los elementos secundarios que coinciden con los nombres de los parámetros. En el ejemplo siguiente, <TableHeader>...</TableHeader> y <RowTemplate>...<RowTemplate> proporcionan plantillas de RenderFragment<TValue> para TableHeader y RowTemplate del componente TableTemplate.
Especifique el atributo Context en el elemento de componente si quiere especificar el nombre del parámetro de contenido para el contenido secundario implícito (sin ningún elemento secundario de ajuste). En el ejemplo siguiente, el atributo Context aparece en el elemento TableTemplate y se aplica a todos los parámetros RenderFragment<TValue> de la plantilla.
Pages/Pets1.razor:
@page "/pets1"
<h1>Pets</h1>
<TableTemplate Items="pets" Context="pet">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@pet.PetId</td>
<td>@pet.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string? Name { get; set; }
}
}
Como alternativa, puede cambiar el nombre del parámetro mediante el atributo Context en el elemento secundario RenderFragment<TValue>. En el ejemplo siguiente, Context se establece en RowTemplate en lugar de en TableTemplate:
Pages/Pets2.razor:
@page "/pets2"
<h1>Pets</h1>
<TableTemplate Items="pets">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate Context="pet">
<td>@pet.PetId</td>
<td>@pet.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string? Name { get; set; }
}
}
Los argumentos de componente de tipo RenderFragment<TValue> tienen un parámetro implícito denominado context que se puede usar. En el ejemplo siguiente, no se ha establecido Context. @context.{PROPERTY} proporciona valores de mascotas a la plantilla, donde {PROPERTY} es una propiedad de Pet:
Pages/Pets3.razor:
@page "/pets3"
<h1>Pets</h1>
<TableTemplate Items="pets">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@context.PetId</td>
<td>@context.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string? Name { get; set; }
}
}
Al usar componentes de tipo genérico, el parámetro de tipo se infiere, si es posible. Sin embargo, puede especificar explícitamente el tipo con un atributo que tenga un nombre que coincida con el parámetro de tipo, que es TItem en el ejemplo anterior:
Pages/Pets4.razor:
@page "/pets4"
<h1>Pets</h1>
<TableTemplate Items="pets" TItem="Pet">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@context.PetId</td>
<td>@context.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string? Name { get; set; }
}
}
Inferencia de tipos genéricos en función de los componentes antecesores
Un componente antecesor puede organizar en cascada un parámetro de tipo por nombre hasta los descendientes mediante el atributo CascadingTypeParameter. Este atributo permite que una inferencia de tipos genérico use el parámetro de tipo especificado de forma automática con los descendientes que tengan un parámetro de tipo con el mismo nombre.
Por ejemplo, el siguiente componente Chart recibe datos de precios de las acciones y organiza en cascada un parámetro de tipo genérico denominado TLineData hasta sus componentes descendientes.
Shared/Chart.razor:
@attribute [CascadingTypeParameter(nameof(TLineData))]
@typeparam TLineData
...
@code {
[Parameter]
public IEnumerable<TLineData> Data { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
}
Shared/Line.razor:
@typeparam TLineData
...
@code {
[Parameter]
public string Title { get; set; }
[Parameter]
public decimal Value { get; set; }
}
Cuando se usa el componente Chart, TLineData no se especifica para cada componente Line del gráfico.
Pages/StockPriceHistory.razor:
@page "/stock-price-history"
<Chart Data="stockPriceHistory.GroupBy(x => x.Date)">
<Line Title="Open" Value="day => day.Values.First()" />
<Line Title="High" Value="day => day.Values.Max()" />
<Line Title="Low" Value="day => day.Values.Min()" />
<Line Title="Close" Value="day => day.Values.Last()" />
</Chart>
Nota
La compatibilidad con Razor en Visual Studio Code no se ha actualizado para admitir esta característica, por lo que puede recibir errores incorrectos, aunque el proyecto se compile correctamente. Este problema se corregirá en una versión futura de las herramientas.
Al agregar @attribute [CascadingTypeParameter(...)] a un componente, el argumento de tipo genérico especificado se usa automáticamente en los descendientes que:
- Están anidados como contenido secundario del componente en el mismo documento
.razor. - Declaren también un parámetro
@typeparamexactamente con el mismo nombre. - No tienen otro valor suministrado o inferido para el parámetro de tipo. Si se suministra o infiere otro valor, este tiene prioridad sobre el tipo genérico en cascada.
Al recibir un parámetro de tipo en cascada, los componentes obtienen el valor del parámetro del antecesor más cercano que tenga un parámetro CascadingTypeParameter con un nombre coincidente. Los parámetros de tipo genérico en cascada se invalidan en un subárbol determinado.
La búsqueda de coincidencias solo se realiza por nombre. Por lo tanto, se recomienda evitar un parámetro de tipo genérico en cascada con un nombre genérico, por ejemplo, T o TItem. Si un desarrollador opta por organizar en cascada un parámetro de tipo, promete implícitamente que su nombre es lo suficientemente exclusivo como para no entrar en conflicto con otros parámetros de tipo en cascada de componentes no relacionados.
Se admiten los tipos genéricos con restricciones de tipo where:
@typeparam TEntity where TEntity : IEntity
Para más información, consulte los siguientes artículos.
Recursos adicionales
Los componentes con plantilla son componentes que aceptan una o varias plantillas de interfaz de usuario como parámetros, que se pueden usar como parte de la lógica de representación del componente. Los componentes con plantilla permiten crear componentes de nivel superior que son más reutilizables que los componentes normales. Estos son un par de ejemplos:
- Un componente de tabla que permite a un usuario especificar plantillas para el encabezado, las filas y el pie de página de la tabla.
- Un componente de lista que permite a un usuario especificar una plantilla para representar elementos en una lista.
Un componente con plantilla se define especificando uno o más parámetros de componente de tipo RenderFragment o RenderFragment<TValue>. Un fragmento de representación representa un segmento de interfaz de usuario que se va a representar. RenderFragment<TValue> toma un parámetro de tipo que se puede especificar cuando se invoca el fragmento de representación.
A menudo, los componentes con plantilla tienen un tipo genérico, como se muestra en el siguiente componente de TableTemplate. El tipo genérico <T> en este ejemplo se usa para representar valores de IReadOnlyList<T>, que en este caso es una serie de filas de mascotas en un componente que muestra una tabla de mascotas.
Shared/TableTemplate.razor:
@typeparam TItem
<table class="table">
<thead>
<tr>@TableHeader</tr>
</thead>
<tbody>
@foreach (var item in Items)
{
<tr>@RowTemplate(item)</tr>
}
</tbody>
</table>
@code {
[Parameter]
public RenderFragment TableHeader { get; set; }
[Parameter]
public RenderFragment<TItem> RowTemplate { get; set; }
[Parameter]
public IReadOnlyList<TItem> Items { get; set; }
}
Al usar un componente con plantilla, los parámetros de plantilla se pueden especificar utilizando los elementos secundarios que coinciden con los nombres de los parámetros. En el ejemplo siguiente, <TableHeader>...</TableHeader> y <RowTemplate>...<RowTemplate> proporcionan plantillas de RenderFragment<TValue> para TableHeader y RowTemplate del componente TableTemplate.
Especifique el atributo Context en el elemento de componente si quiere especificar el nombre del parámetro de contenido para el contenido secundario implícito (sin ningún elemento secundario de ajuste). En el ejemplo siguiente, el atributo Context aparece en el elemento TableTemplate y se aplica a todos los parámetros RenderFragment<TValue> de la plantilla.
Pages/Pets1.razor:
@page "/pets1"
<h1>Pets</h1>
<TableTemplate Items="pets" Context="pet">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@pet.PetId</td>
<td>@pet.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string Name { get; set; }
}
}
Como alternativa, puede cambiar el nombre del parámetro mediante el atributo Context en el elemento secundario RenderFragment<TValue>. En el ejemplo siguiente, Context se establece en RowTemplate en lugar de en TableTemplate:
Pages/Pets2.razor:
@page "/pets2"
<h1>Pets</h1>
<TableTemplate Items="pets">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate Context="pet">
<td>@pet.PetId</td>
<td>@pet.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string Name { get; set; }
}
}
Los argumentos de componente de tipo RenderFragment<TValue> tienen un parámetro implícito denominado context que se puede usar. En el ejemplo siguiente, no se ha establecido Context. @context.{PROPERTY} proporciona valores de mascotas a la plantilla, donde {PROPERTY} es una propiedad de Pet:
Pages/Pets3.razor:
@page "/pets3"
<h1>Pets</h1>
<TableTemplate Items="pets">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@context.PetId</td>
<td>@context.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string Name { get; set; }
}
}
Al usar componentes de tipo genérico, el parámetro de tipo se infiere, si es posible. Sin embargo, puede especificar explícitamente el tipo con un atributo que tenga un nombre que coincida con el parámetro de tipo, que es TItem en el ejemplo anterior:
Pages/Pets4.razor:
@page "/pets4"
<h1>Pets</h1>
<TableTemplate Items="pets" TItem="Pet">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@context.PetId</td>
<td>@context.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string Name { get; set; }
}
}
Recursos adicionales
Los componentes con plantilla son componentes que aceptan una o varias plantillas de interfaz de usuario como parámetros, que se pueden usar como parte de la lógica de representación del componente. Los componentes con plantilla permiten crear componentes de nivel superior que son más reutilizables que los componentes normales. Estos son un par de ejemplos:
- Un componente de tabla que permite a un usuario especificar plantillas para el encabezado, las filas y el pie de página de la tabla.
- Un componente de lista que permite a un usuario especificar una plantilla para representar elementos en una lista.
Un componente con plantilla se define especificando uno o más parámetros de componente de tipo RenderFragment o RenderFragment<TValue>. Un fragmento de representación representa un segmento de interfaz de usuario que se va a representar. RenderFragment<TValue> toma un parámetro de tipo que se puede especificar cuando se invoca el fragmento de representación.
A menudo, los componentes con plantilla tienen un tipo genérico, como se muestra en el siguiente componente de TableTemplate. El tipo genérico <T> en este ejemplo se usa para representar valores de IReadOnlyList<T>, que en este caso es una serie de filas de mascotas en un componente que muestra una tabla de mascotas.
Shared/TableTemplate.razor:
@typeparam TItem
<table class="table">
<thead>
<tr>@TableHeader</tr>
</thead>
<tbody>
@foreach (var item in Items)
{
<tr>@RowTemplate(item)</tr>
}
</tbody>
</table>
@code {
[Parameter]
public RenderFragment TableHeader { get; set; }
[Parameter]
public RenderFragment<TItem> RowTemplate { get; set; }
[Parameter]
public IReadOnlyList<TItem> Items { get; set; }
}
Al usar un componente con plantilla, los parámetros de plantilla se pueden especificar utilizando los elementos secundarios que coinciden con los nombres de los parámetros. En el ejemplo siguiente, <TableHeader>...</TableHeader> y <RowTemplate>...<RowTemplate> proporcionan plantillas de RenderFragment<TValue> para TableHeader y RowTemplate del componente TableTemplate.
Especifique el atributo Context en el elemento de componente si quiere especificar el nombre del parámetro de contenido para el contenido secundario implícito (sin ningún elemento secundario de ajuste). En el ejemplo siguiente, el atributo Context aparece en el elemento TableTemplate y se aplica a todos los parámetros RenderFragment<TValue> de la plantilla.
Pages/Pets1.razor:
@page "/pets1"
<h1>Pets</h1>
<TableTemplate Items="pets" Context="pet">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@pet.PetId</td>
<td>@pet.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new List<Pet>
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string Name { get; set; }
}
}
Como alternativa, puede cambiar el nombre del parámetro mediante el atributo Context en el elemento secundario RenderFragment<TValue>. En el ejemplo siguiente, Context se establece en RowTemplate en lugar de en TableTemplate:
Pages/Pets2.razor:
@page "/pets2"
<h1>Pets</h1>
<TableTemplate Items="pets">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate Context="pet">
<td>@pet.PetId</td>
<td>@pet.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new List<Pet>
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string Name { get; set; }
}
}
Los argumentos de componente de tipo RenderFragment<TValue> tienen un parámetro implícito denominado context que se puede usar. En el ejemplo siguiente, no se ha establecido Context. @context.{PROPERTY} proporciona valores de mascotas a la plantilla, donde {PROPERTY} es una propiedad de Pet:
Pages/Pets3.razor:
@page "/pets3"
<h1>Pets</h1>
<TableTemplate Items="pets">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@context.PetId</td>
<td>@context.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new List<Pet>
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string Name { get; set; }
}
}
Al usar componentes de tipo genérico, el parámetro de tipo se infiere, si es posible. Sin embargo, puede especificar explícitamente el tipo con un atributo que tenga un nombre que coincida con el parámetro de tipo, que es TItem en el ejemplo anterior:
Pages/Pets4.razor:
@page "/pets4"
<h1>Pets</h1>
<TableTemplate Items="pets" TItem="Pet">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@context.PetId</td>
<td>@context.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new List<Pet>
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string Name { get; set; }
}
}