Жизненный цикл компонента Blazor: полное руководство
Я использую Blazor уже некоторое время, и, честно говоря, методы жизненного цикла поначалу меня смутили. OnInitialized против OnInitializedAsync? OnParametersSet против OnAfterRender? Когда StateHasChanged фактически запускает повторный рендеринг? После многих проб и ошибок у меня наконец появилась твердая мысленная модель всего этого.
Краткий обзор жизненного цикла
При рендеринге компонента Blazor он проходит через следующие методы по порядку:
- [[[ТОК_5]]]
- [[[ТОК_6]]] / [[[ТОК_7]]]
- [[[ТОК_8]]] / [[[ТОК_9]]]
- [[[ТОК_10]]] / [[[ТОК_11]]]
- [[[ТОК_12]]] / [[[ТОК_13]]]
Давайте пройдемся по каждому.
УстановитьПараметрыАсинхронно
Это самый первый вызываемый метод. Он получает необработанный ParameterView от родительского компонента. В большинстве случаев вы не переопределяете это — Blazor автоматически обрабатывает параметры сопоставления. Но если вам нужна обработка или проверка пользовательских параметров перед их назначением:
public override async Task SetParametersAsync(ParameterView parameters)
{
// Custom logic before parameters are set
if (parameters.TryGetValue<string>("Title", out var title))
{
Console.WriteLine($"Title is being set to: {title}");
}
await base.SetParametersAsync(parameters);
}
Я использовал это ровно один раз в реальном проекте. Большую часть времени вы пропустите это.
OnInitialized/OnInitializedAsync
Здесь вы выполняете работу по настройке — загружаете данные, инициализируете службы, устанавливаете значения по умолчанию. Он запускается один раз при первом создании компонента.
@code {
private List<Product> products = new();
protected override async Task OnInitializedAsync()
{
products = await Http.GetFromJsonAsync<List<Product>>("api/products");
}
}
Одна вещь, которая меня смутила: на Blazor Server OnInitializedAsync вызывается дважды во время предварительного рендеринга. Первый раз во время предварительной отрисовки на стороне сервера, а второй раз — при установке соединения SignalR. Если ваш вызов API является дорогостоящим, вы можете решить эту проблему:
private bool isPrerendering = true;
protected override async Task OnInitializedAsync()
{
products = await Http.GetFromJsonAsync<List<Product>>("api/products");
isPrerendering = false;
}
Или, что еще лучше, используйте PersistentComponentState, чтобы полностью избежать двойного вызова.
OnParametersSet / OnParametersSetAsync
Это срабатывает каждый раз, когда родительский компонент выполняет повторную визуализацию и передает новые значения параметров. Он также срабатывает после OnInitialized. Это подходящее место для реагирования на изменения параметров:
[Parameter]
public int CategoryId { get; set; }
private int previousCategoryId;
private List<Product> products = new();
protected override async Task OnParametersSetAsync()
{
if (CategoryId != previousCategoryId)
{
previousCategoryId = CategoryId;
products = await Http.GetFromJsonAsync<List<Product>>(
$"api/products?category={CategoryId}");
}
}
Проверка на CategoryId != previousCategoryId важна — без нее вы бы перезагружали данные каждый раз, когда родительский элемент выполняет повторную визуализацию, даже если категория не изменилась.
OnAfterRender / OnAfterRenderAsync
Это срабатывает после того, как компонент отображается в DOM. Параметр firstRender сообщает вам, является ли это первоначальным рендерингом. Это место для вызовов JS Interop, поскольку на этом этапе существуют элементы DOM:
[Inject]
private IJSRuntime JS { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("initializeChart", chartElement);
}
}
Я часто использую firstRender, чтобы избежать повторной инициализации библиотек JavaScript при каждом повторном рендеринге. Если вы настраиваете прослушиватели событий, библиотеки диаграмм или что-то, что напрямую касается DOM, это то, что вам нужно.
ДолженRender
Этот менее известен, но очень полезен. Он контролирует, будет ли компонент выполнять повторную визуализацию при вызове StateHasChanged:
private bool shouldRender = true;
protected override bool ShouldRender() => shouldRender;
private void HeavyOperation()
{
shouldRender = false;
// Do a bunch of state changes without triggering renders
for (int i = 0; i < 1000; i++)
{
items[i].Process();
}
shouldRender = true;
StateHasChanged(); // Now render once with all changes
}
Я использовал это для оптимизации компонентов, обрабатывающих большие списки. Вместо повторного рендеринга при каждом изменении элемента вы выполняете пакетную обработку обновлений и выполняете рендеринг один раз в конце.
Dispose/DisposeAsync
Когда компонент удаляется из пользовательского интерфейса, вам следует очистить все ресурсы. Реализуйте IDisposable или IAsyncDisposable:
@implements IAsyncDisposable
@code {
private Timer? timer;
private IJSObjectReference? jsModule;
protected override void OnInitialized()
{
timer = new Timer(OnTick, null, 0, 1000);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
jsModule = await JS.InvokeAsync<IJSObjectReference>("import", "./timer.js");
}
}
public async ValueTask DisposeAsync()
{
timer?.Dispose();
if (jsModule is not null)
{
await jsModule.DisposeAsync();
}
}
}
Общие вещи, которые нужно удалить: таймеры, подписки на события, ссылки на модули JS, CancellationTokenSource и любые созданные вами службы IDisposable.
СостояниеИзмененоЭто не метод жизненного цикла, но он тесно связан. Он сообщает Blazor: «Эй, мое состояние изменилось, пожалуйста, перерисуйте меня». Blazor вызывает его автоматически после обработчиков событий, но иногда вам нужно вызвать его вручную — обычно, когда состояние изменяется вне обычного потока событий Blazor:
private async Task StartPolling()
{
while (!cts.Token.IsCancellationRequested)
{
data = await Http.GetFromJsonAsync<Data>("api/data");
StateHasChanged(); // Manual call needed since this isn't a Blazor event
await Task.Delay(5000, cts.Token);
}
}
Одно важное замечание: если вы обновляетесь из фонового потока на Blazor Server, используйте InvokeAsync:
await InvokeAsync(() =>
{
data = newData;
StateHasChanged();
});
Полная картина
Вот порядок, в котором все происходит:
Component created
└─ SetParametersAsync
└─ OnInitialized / OnInitializedAsync
└─ OnParametersSet / OnParametersSetAsync
└─ Render
└─ OnAfterRender(firstRender: true)
Parameter change from parent
└─ SetParametersAsync
└─ OnParametersSet / OnParametersSetAsync
└─ ShouldRender?
└─ Render
└─ OnAfterRender(firstRender: false)
Component removed
└─ Dispose / DisposeAsync
Если у вас в голове этот поток, отладка проблем жизненного цикла становится намного проще.
Надеюсь, вам понравился пост! Не стесняйтесь обращаться ко мне в любой социальной сети по адресу @emimontesdeoca.