Ciclo de vida do componente Blazor: o guia completo

· 5 min de leitura

Já uso o Blazor há algum tempo e, honestamente, os métodos de ciclo de vida me confundiram no início. OnInitialized versus OnInitializedAsync? OnParametersSet versus OnAfterRender? Quando StateHasChanged realmente aciona uma nova renderização? Depois de muitas tentativas e erros, finalmente tenho um modelo mental sólido para tudo isso.

O ciclo de vida em resumo

Quando um componente Blazor é renderizado, ele passa por estes métodos em ordem:

  1. SetParametersAsync
  2. OnInitialized / OnInitializedAsync
  3. OnParametersSet / OnParametersSetAsync
  4. OnAfterRender / OnAfterRenderAsync
  5. Dispose / DisposeAsync

Vamos examinar cada um deles.

#SetParametersAsync

Este é o primeiro método chamado. Ele recebe o ParameterView bruto do componente pai. Na maioria das vezes você não substitui isso - o Blazor lida com os parâmetros de mapeamento automaticamente. Mas se você precisar de manipulação ou validação de parâmetros personalizados antes de serem atribuídos:

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);
}

Eu usei isso exatamente uma vez em um projeto real. Na maioria das vezes você vai pular isso.

#OnInitialized/OnInitializedAsync

É aqui que você faz o trabalho de configuração – carrega dados, inicializa serviços, define valores padrão. Ele é executado uma vez quando o componente é criado pela primeira vez.

@code {
    private List<Product> products = new();

    protected override async Task OnInitializedAsync()
    {
        products = await Http.GetFromJsonAsync<List<Product>>("api/products");
    }
}

Uma coisa que me surpreendeu: no Blazor Server, OnInitializedAsync é chamado duas vezes durante a pré-renderização. A primeira vez durante a pré-renderização do lado do servidor e a segunda vez quando a conexão SignalR é estabelecida. Se sua chamada de API for cara, você pode querer cuidar disso:

private bool isPrerendering = true;

protected override async Task OnInitializedAsync()
{
    products = await Http.GetFromJsonAsync<List<Product>>("api/products");
    isPrerendering = false;
}

Ou melhor ainda, use PersistentComponentState para evitar totalmente a chamada dupla.

#OnParametersSet/OnParametersSetAsync

Isso é acionado sempre que o componente pai é renderizado novamente e passa novos valores de parâmetro. Ele também dispara após OnInitialized. Este é o lugar certo para reagir às alterações de parâmetros:

[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}");
    }
}

A verificação de CategoryId != previousCategoryId é importante - sem ela, você recarregaria os dados toda vez que o pai fosse renderizado novamente, mesmo que a categoria não mudasse.

#OnAfterRender/OnAfterRenderAsync

Isso é acionado depois que o componente é renderizado no DOM. O parâmetro firstRender informa se é a renderização inicial. Este é o local para chamadas de interoperabilidade JS, já que os elementos DOM existem neste ponto:

[Inject]
private IJSRuntime JS { get; set; }

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        await JS.InvokeVoidAsync("initializeChart", chartElement);
    }
}

Eu uso muito firstRender para evitar a reinicialização de bibliotecas JavaScript em cada nova renderização. Se você estiver configurando ouvintes de eventos, bibliotecas de gráficos ou qualquer coisa que toque diretamente no DOM, é aqui que tudo vai.

Deverenderizar

Este é menos conhecido, mas super útil. Ele controla se um componente é renderizado novamente quando StateHasChanged é chamado:

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
}

Usei isso para otimizar componentes que processam listas grandes. Em vez de renderizar novamente a cada alteração de item, você agrupa as atualizações e renderiza uma vez no final.

Dispose/DisposeAsync

Quando um componente é removido da UI, você deve limpar todos os recursos. Implemente IDisposable ou 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();
        }
    }
}

Coisas comuns para descartar: temporizadores, assinaturas de eventos, referências de módulos JS, CancellationTokenSource e quaisquer serviços IDisposable que você criou.

#EstadoHasChangedEste não é um método de ciclo de vida, mas está intimamente relacionado. Diz ao Blazor “ei, meu estado mudou, por favor, me renderize novamente.” O Blazor o chama automaticamente após manipuladores de eventos, mas às vezes você precisa chamá-lo manualmente — normalmente quando o estado muda fora do fluxo de eventos normal do 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);
    }
}

Uma observação importante: se você estiver atualizando a partir de um thread em segundo plano no Blazor Server, use InvokeAsync:

await InvokeAsync(() =>
{
    data = newData;
    StateHasChanged();
});

A imagem completa

Esta é a ordem em que tudo acontece:

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

Depois de ter esse fluxo em mente, a depuração de problemas do ciclo de vida se torna muito mais fácil.

Espero que você tenha gostado da postagem! Sinta-se à vontade para entrar em contato comigo em qualquer mídia social em @emimontesdeoca.

Recursos