Исходя из проблематики предыдующей статьи, у меня появились следующие наработки.
30% моих компонентов инициализируются через метод OnInitialized(), а все остальные 70% через оверрайдинг метода OnComponentMountAsync(). Такого метода не существует, но когда я поддержал его в своей компонентной базе, то перестал задумываться о проблемах с построением асинхронных кейсов внутри приложения.
В качестве основы я создал интерфейс IAsyncComponentBase.cs, который оговаривает, что компоненты, наследующие данный интерфейс имеет доступ к специальному делегату. Его вызов приводит к попытке перерендеривания страницы.
using System.Threading.Tasks;
namespace blazor_lifetime.Models
{
public delegate Task RenderHandleAsync();
public interface IAsyncContentRenderer
{
RenderHandleAsync RenderChildContentAsync { get; set; }
}
}
Для того чтобы обеспечить прозрачную поддержку асинхронного рендеринга компонента, необходимо создать вспомогательный компонент AsyncContentSupport.razor. Он будет оберткой над контентом асинхронных компонентов.
@using blazor_lifetime.Models
@if (_isContentVisible)
{
@ChildContent
}
else
{
@if (LoadingContent != null)
{
@LoadingContent
}
}
@code{
private bool _isContentVisible = false;
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public RenderFragment LoadingContent { get; set; }
[Parameter] public IAsyncComponentBase Component { get; set; }
protected override void OnInitialized()
{
if (Component == null)
{
throw new Exception(nameof(Component));
}
Component.RenderChildContentAsync += OnRenderChildContentAsync;
}
protected async Task OnRenderChildContentAsync()
{
_isContentVisible = true;
await InvokeAsync(StateHasChanged);
}
}
Далее опишем замену базовому классу компонента. Сначала я использовал наследование от ComponentBase, а затем, когда появился OwningComponentBase, я начал использовать его.
Кстати, использование OwningComponentBase очень полезно при проектировании приложений на базе Blazor Server Side, потому что у вас всегда есть IDisposable интерфейс.
И да, еще одно мелкое замечание. ComponentBase выгружается не сразу, что не иногда не очень то и удобно. А вот Owning… версия крайне позитивна в данном вопросе. Как только пользователь закрывает страницу с компонентом, то у него сразу происходит вызов Dispose() и выгрузка самого компонента.
Я назвал этот класс AdvancedComponentBase и именно он должен имплемнтировать ранее указанный мною интерфейс IAsyncComponentBase.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using blazor_lifetime.Models;
namespace blazor_lifetime.Shared
{
public class AdvancedComponentBase : OwningComponentBase, IAsyncComponentBase
{
[Parameter] public virtual RenderFragment ChildContent { get; set; }
public RenderHandleAsync RenderChildContentAsync { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await OnComponentMountAsync().ConfigureAwait(false);
if (RenderChildContentAsync != null)
{
await RenderChildContentAsync();
}
}
else
{
await Task.CompletedTask;
}
}
protected virtual Task OnComponentMountAsync()
=> Task.CompletedTask;
}
}
А теперь на красочном примере компонента я хочу объяснить, как же теперь пользоваться этим компонентом поддержки и как он работает внутри.
Как только страница впервые начинает отрисовываться, то наш вспомогательный компонент отрисовывает либо LoadingContent либо не отрисовывает вообще ничего. Также он передает ручку управления собой в основной компонент (RenderHandler=@this) и вызывает у основного компонента OnComponentMountAsync().
Завершение выполенения OnComponentMountAsync() приводит к тому, что наш основной компонент пытается теперь вызывать отрисовку ChildContent в вспомогательном интерфейсе, в котором и расположена та часть кода, которая крайне зависима от асинхронности.
@inherits FirstLevelAdvancedComponentAsyncModel
<AsyncContentSupport RenderHandler="@this">
<LoadingContent>
Please wait... Content is loading...
</LoadingContent>
<ChildContent>
<div>First Component Start</div>
<SecondLevelComponentAsync Parameter=@AsyncParameter />
<div>First Component End</div>
</ChildContent>
</AsyncContentSupport>
Такой подход конечно требует некоторое время на разбор и применение в работе. Однако, дальнейшее его использование открывает самые сильные стороны этого подхода.
- Внутренние компоненты всегда будут получать параметры, которые появляются в ходе выполнения асинхронного кода
- Теперь в компонент невероятно просто встроить статический контент, который будет показываться при подгрузке — это может быть и обычный прогресс бар или же аккуратные заглушки
- При данном подходе, первый рендер уже закончился, а значит обращение к JsInvoke внутри OnComponentMountAsync() не будет выбрасывать исключений
В качестве примера, все виды подходов к рендерингу были опубликованы в репозитории: https://git.defend.pw/defend/blazor-lifetime
1 ответ к “Упрощаем Lifetime Blazor приложения”
Если по окончании вызова асинхронного метода добавить строку
StateHasChanged();
не тоже самое получается?