Representación de componentes de Razor de ASP.NET Core
Nota
Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión .NET 8 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión .NET 8 de este artículo.
En este artículo se explica la representación del componente Razor en aplicaciones ASP.NET Core Blazor, incluido cuándo llamar a StateHasChanged para desencadenar manualmente un componente para representarlo.
Convenciones de representación para ComponentBase
Los componentes deben representarse la primera vez que se agregan a la jerarquía de componentes por parte de su componente primario. Esta es la única vez que debe representarse un componente. Los componentes pueden representarse en otras ocasiones según su lógica y sus convenciones propias.
De forma predeterminada, los componentes de Razor se heredan de la clase base ComponentBase, que contiene la lógica para desencadenar la nueva representación en las siguientes ocasiones:
- Después de aplicar un conjunto actualizado de parámetros desde un componente primario.
- Después de aplicar un valor actualizado para un parámetro en cascada.
- Después de la notificación de un evento y la invocación de uno de sus propios controladores de eventos.
- Después de una llamada a su propio método StateHasChanged (vea Ciclo de vida de componentes Razor de ASP.NET Core). Para una guía sobre cómo evitar sobrescribir parámetros de componentes secundarios cuando se llama a StateHasChanged en un componente primario, consulte Evitar sobrescribir parámetros en ASP.NET Core Blazor.
Los componentes heredados de ComponentBase omiten las nuevas representaciones debido a las actualizaciones de parámetros si se cumple alguna de las condiciones siguientes:
Todos los parámetros proceden de un conjunto de tipos conocidos† o cualquier tipo primitivo que no haya cambiado desde que se estableció el conjunto anterior de parámetros.
†El marco Blazor usa un conjunto de reglas y comprobaciones de tipo de parámetro explícito integradas para la detección de cambios. Estas reglas y los tipos están sujetos a cambios en cualquier momento. Para obtener más información, consulte
ChangeDetection
API en el origen de referencia de ASP.NET Core.Nota
Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, use la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).
La invalidación del método del componente
ShouldRender
devuelvefalse
(la implementación predeterminadaComponentBase
siempre devuelvetrue
).
Control del flujo de representación
En la mayoría de los casos, las convenciones de ComponentBase dan como resultado el subconjunto correcto de nuevas representaciones de componentes después de que se produzca un evento. Normalmente, no es necesario que los desarrolladores proporcionen ninguna lógica manual para indicar al marco qué componentes se van a volver a representar y cuándo. El efecto general de las convenciones del marco es que el componente que recibe un evento se vuelva a representar a sí mismo, lo que desencadena de forma recursiva la nueva representación de los componentes descendientes cuyos valores de parámetro puedan haber cambiado.
Para más información sobre las implicaciones de rendimiento de las convenciones del marco y cómo optimizar la jerarquía de componentes de una aplicación para la representación, vea Procedimientos recomendados de rendimiento de Blazor en ASP.NET Core.
Representación de streaming
Use representación de streaming con representación estática del lado servidor (SSR estático) o representación previa para transmitir actualizaciones de contenido en el flujo de respuesta y mejorar la experiencia del usuario para los componentes que realizan tareas asincrónicas de larga duración para representarse por completo.
Por ejemplo, considere un componente que realiza una consulta de base de datos de larga duración o una llamada API web para representar los datos cuando se carga la página. Normalmente, las tareas asincrónicas ejecutadas como parte de la representación de un componente del lado servidor deben completarse antes de enviar la respuesta representada, lo que puede retrasar la carga de la página. Cualquier retraso significativo en la representación de la página daña la experiencia del usuario. Para mejorar la experiencia del usuario, la representación de streaming empieza representando rápidamente toda la página con contenido de marcador de posición mientras se ejecutan las operaciones asincrónicas. Una vez completadas las operaciones, el contenido actualizado se envía al cliente en la misma conexión de respuesta y se revisa en el DOM.
La representación de streaming requiere que el servidor evite almacenar en búfer la salida. Los datos de respuesta deben fluir al cliente a medida que se generan los datos. En el caso de los hosts que aplican el almacenamiento en búfer, la representación de streaming se degrada correctamente y la página se carga sin representación de streaming.
Para transmitir actualizaciones de contenido al usar la representación estática del lado servidor (SSR estático) o la representación previa, aplique el [StreamRendering(true)]
atributo al componente. La representación de streaming debe estar habilitada explícitamente porque las actualizaciones transmitidas pueden provocar que el contenido de la página cambie. Los componentes sin el atributo adoptan automáticamente la representación de streaming si el componente primario usa la característica. Pase false
al atributo de un componente secundario para deshabilitar la característica en ese momento y más abajo en el subárbol del componente. El atributo es funcional cuando se aplica a los componentes proporcionados por una Razor biblioteca de clases.
El ejemplo siguiente se basa en el componente Weather
de una aplicación creada a partir de la Blazor plantilla de proyecto aplicación web. La llamada para Task.Delay simula la recuperación de datos meteorológicos de forma asincrónica. El componente representa inicialmente el contenido del marcador de posición ("Loading...
") sin esperar a que se complete el retraso asincrónico. Cuando se completa el retraso asincrónico y se genera el contenido de los datos meteorológicos, el contenido se transmite a la respuesta y se revisa en la tabla de previsión meteorológica.
Weather.razor
:
@page "/weather"
@attribute [StreamRendering(true)]
...
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
...
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
...
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
await Task.Delay(500);
...
forecasts = ...
}
}
Supresión de la actualización de la interfaz de usuario (ShouldRender
)
Se llama a ShouldRender cada vez que se representa un componente. Invalide ShouldRender para administrar la actualización de la interfaz de usuario. Si la implementación devuelve true
, la interfaz de usuario se actualiza.
Aunque se invalide ShouldRender, el componente siempre se representa inicialmente.
ControlRender.razor
:
@page "/control-render"
<PageTitle>Control Render</PageTitle>
<h1>Control Render Example</h1>
<label>
<input type="checkbox" @bind="shouldRender" />
Should Render?
</label>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private bool shouldRender = true;
protected override bool ShouldRender()
{
return shouldRender;
}
private void IncrementCount()
{
currentCount++;
}
}
@page "/control-render"
<label>
<input type="checkbox" @bind="shouldRender" />
Should Render?
</label>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private bool shouldRender = true;
protected override bool ShouldRender()
{
return shouldRender;
}
private void IncrementCount()
{
currentCount++;
}
}
@page "/control-render"
<label>
<input type="checkbox" @bind="shouldRender" />
Should Render?
</label>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private bool shouldRender = true;
protected override bool ShouldRender()
{
return shouldRender;
}
private void IncrementCount()
{
currentCount++;
}
}
@page "/control-render"
<label>
<input type="checkbox" @bind="shouldRender" />
Should Render?
</label>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private bool shouldRender = true;
protected override bool ShouldRender()
{
return shouldRender;
}
private void IncrementCount()
{
currentCount++;
}
}
@page "/control-render"
<label>
<input type="checkbox" @bind="shouldRender" />
Should Render?
</label>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private bool shouldRender = true;
protected override bool ShouldRender()
{
return shouldRender;
}
private void IncrementCount()
{
currentCount++;
}
}
Para más información sobre los procedimientos recomendados de rendimiento relacionados con ShouldRender, vea Procedimientos recomendados de rendimiento de Blazor en ASP.NET Core.
Cuándo llamar a StateHasChanged
Llamar a StateHasChanged permite desencadenar una representación en cualquier momento. Pero debe evitar llamar a StateHasChanged de forma innecesaria, lo que es un error común, ya que impone costos de representación innecesarios.
El código no debe llamar a StateHasChanged en los casos siguientes:
- Se controlan eventos de manera rutinaria, ya sea de forma sincrónica o asincrónica, porque ComponentBase desencadena una representación para la mayoría de los controladores de eventos rutinarios.
- Se implementa una lógica típica del ciclo de vida, como
OnInitialized
oOnParametersSetAsync
, ya sea de forma sincrónica o asincrónica, porque ComponentBase desencadena una representación para los eventos de ciclo de vida típicos.
Sin embargo, es posible que tenga sentido llamar a StateHasChanged en los casos descritos en las secciones siguientes de este artículo:
- Un controlador asincrónico implica varias fases asincrónicas
- Recepción de una llamada de algo externo al sistema de control de eventos y representación de Blazor
- Para representar el componente fuera del subárbol que se vuelve a representar mediante un evento concreto
Un controlador asincrónico implica varias fases asincrónicas
Debido al modo en el que se definen las tareas en .NET, un receptor de una instancia de Task solo puede observar su finalización final, no los estados asincrónicos intermedios. Por tanto, ComponentBase solo puede desencadenar la nueva representación cuando se devuelve Task por primera vez y cuando se completa Task finalmente. El marco de trabajo no puede saber que tiene que volver a representar un componente en otros puntos intermedios, como cuando IAsyncEnumerable<T>devuelve datos en una serie de valores Task
s intermedios. Si quiere repetir la representación en puntos intermedios, llame a StateHasChanged en esos puntos.
Tenga en cuenta el siguiente componente de CounterState1
, que actualiza el recuento cuatro veces cada vez que se ejecuta el método de IncrementCount
:
- Las representaciones automáticas se producen después del primer y último incremento de
currentCount
. - Las representaciones manuales se desencadenan mediante llamadas a StateHasChanged cuando el marco no desencadena automáticamente nuevas representaciones en puntos de procesamiento intermedios en los que
currentCount
se incrementa.
CounterState1.razor
:
@page "/counter-state-1"
<PageTitle>Counter State 1</PageTitle>
<h1>Counter State Example 1</h1>
<p>
Current count: @currentCount
</p>
<p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private async Task IncrementCount()
{
currentCount++;
// Renders here automatically
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
// Renders here automatically
}
}
@page "/counter-state-1"
<p>
Current count: @currentCount
</p>
<p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private async Task IncrementCount()
{
currentCount++;
// Renders here automatically
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
// Renders here automatically
}
}
@page "/counter-state-1"
<p>
Current count: @currentCount
</p>
<p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private async Task IncrementCount()
{
currentCount++;
// Renders here automatically
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
// Renders here automatically
}
}
@page "/counter-state-1"
<p>
Current count: @currentCount
</p>
<p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private async Task IncrementCount()
{
currentCount++;
// Renders here automatically
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
// Renders here automatically
}
}
@page "/counter-state-1"
<p>
Current count: @currentCount
</p>
<p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private async Task IncrementCount()
{
currentCount++;
// Renders here automatically
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
// Renders here automatically
}
}
Recepción de una llamada de algo externo al sistema de control de eventos y representación de Blazor
ComponentBase solo conoce sus propios métodos de ciclo de vida y los eventos desencadenados por Blazor. ComponentBase no conoce otros eventos que se pueden producir en el código. Por ejemplo, los eventos de C# generados por un almacén de datos personalizado son desconocidos para Blazor. Para que estos eventos desencadenen la nueva representación a fin de mostrar valores actualizados en la interfaz de usuario, llame a StateHasChanged.
Considere el siguiente componente de CounterState2
, el cual usa System.Timers.Timer para actualizar un recuento a intervalos regulares y llama a StateHasChanged para actualizar la interfaz de usuario:
OnTimerCallback
se ejecuta fuera de cualquier flujo de representación o notificación de eventos administrado por Blazor. Por lo tanto,OnTimerCallback
debe llamar a StateHasChanged porque Blazor no es consciente de los cambios encurrentCount
en la devolución de llamada.- El componente implementa IDisposable, donde Timer se desecha cuando el marco llama al método
Dispose
. Para más información, consulte Ciclo de vida de los componentes de ASP.NET Core Razor.
Como la devolución de llamada se invoca fuera del contexto de sincronización de Blazor, el componente debe encapsular la lógica de OnTimerCallback
en ComponentBase.InvokeAsync para moverla al contexto de sincronización del representador. Esto equivale a la serialización en el subproceso de interfaz de usuario en otros marcos de interfaz de usuario. Solo se puede llamar a StateHasChanged desde el contexto de sincronización del representador; en caso contrario se inicia una excepción.
System.InvalidOperationException: "El subproceso actual no está asociado a Dispatcher. Use InvokeAsync() para cambiar la ejecución a Dispatcher al desencadenar la representación o el estado del componente".
CounterState2.razor
:
@page "/counter-state-2"
@using System.Timers
@implements IDisposable
<PageTitle>Counter State 2</PageTitle>
<h1>Counter State Example 2</h1>
<p>
This counter demonstrates <code>Timer</code> disposal.
</p>
<p>
Current count: @currentCount
</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>
Current count: @currentCount
</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>
Current count: @currentCount
</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>
Current count: @currentCount
</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>
Current count: @currentCount
</p>
@code {
private int currentCount = 0;
private Timer timer = new Timer(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
Para representar el componente fuera del subárbol que se vuelve a representar mediante un evento concreto
La interfaz de usuario puede implicar lo siguiente:
- Enviar un evento a un componente.
- Cambiar algún estado.
- Representar un componente completamente diferente que no sea descendiente del componente que recibe el evento.
Una manera de abordar este escenario consiste en que una clase de administración de estado, a menudo como un servicio de inserción de dependencias (DI), se inserte en varios componentes. Cuando un componente llama a un método en el administrador de estados, este genera un evento de C# que, después, lo recibe un componente independiente.
Para conocer los enfoques para administrar el estado, consulte los siguientes recursos:
- Enlace entre más de dos componentes mediante enlaces de datos.
- Pase datos a través de una jerarquía de componentes mediante valores y parámetros en cascada.
- Sección del servicio de contenedor de estado en memoria del lado servidor (equivalente del lado cliente) del artículo Administración de estados.
Para el enfoque del administrador de estado, los eventos de C# están fuera de la canalización de representación de Blazor. Llame a StateHasChanged en otros componentes que desee volver a representar en respuesta a los eventos del administrador de estado.
El enfoque del administrador de estado es similar al caso previo con System.Timers.Timer en la sección anterior. Como la pila de llamadas de ejecución normalmente permanece en el contexto de sincronización del representador, InvokeAsync no suele ser necesario. Llamar a InvokeAsync solo es necesario si la lógica escapa al contexto de sincronización, como al llamar a ContinueWith en Task o esperar por Task con ConfigureAwait(false)
. Para más información consulte la sección de Recepción de una llamada de algo externo al sistema de control de eventos y representación de Blazor.
Indicador de progreso de carga de WebAssembly para Web Apps Blazor
Un indicador de progreso de carga no está presente en una aplicación creada a partir de la plantilla de proyecto aplicación web Blazor. Se planea una nueva característica de indicador de progreso de carga para una versión futura de .NET. Mientras tanto, una aplicación puede adoptar código personalizado para crear un indicador de progreso de carga. Para obtener más información, vea Inicio de Blazor de ASP.NET Core.
Comentarios
https://aka.ms/ContentUserFeedback.
Próximamente: A lo largo de 2024 iremos eliminando gradualmente GitHub Issues como mecanismo de comentarios sobre el contenido y lo sustituiremos por un nuevo sistema de comentarios. Para más información, vea:Enviar y ver comentarios de