Как реализовать шаблон состояния для страниц Blazor, используя несколько компонентов для создания страницы?
У меня есть страница Blazor, в которой используется несколько компонентов. Как я могу реализовать шаблон состояния (в идеале для каждой страницы), который сможет обрабатывать текущее состояние страницы?
В настоящее время у меня есть все состояния и манипуляции с состоянием, выполняемые на странице (и через внедренные службы), но я полагаю, что было бы чище реализовать шаблон состояния, в котором каждая страница имеет какой-то объект состояния, который затем позволяет вам манипулировать страница и ее компоненты в строгом порядке.
В идеале объект State должен реализовывать INotifyPropertyChanged и иметь возможность динамически обновлять свое состояние, но я также не ненавижу идею того, что объект State передает управление состоянием методам объекта, чтобы убедиться, что состояние не просто 1- от обновлено на странице Blazor.
Я уже пытался реализовать какой-то шаблон MVVM, но это вызвало больше вопросов, чем ответов.
Я начал создавать объект State для текущей страницы, над которой работал, но я не уверен, должен ли я просто поместить большую часть логики, которая была на странице Blazor, в объект State, или у меня все еще должен быть какой-то данных, но делегируя тяжелую работу государству.
например: у меня есть некоторый код, который раньше был в функции «OnAfterRenderAsync» на странице Blazor, но я нахожусь в процессе перемещения практически всего, что там есть, в функцию «LoadMatterDetails()» в объекте State, который обрабатывает это . Имеет ли это смысл, или я действительно должен иметь состояние объекта только в объекте состояния, а также писать и читать из объекта состояния, когда доступны определенные фрагменты информации?
public class MatterDetailsState : IMatterDetailsState
{
private readonly IMatterDetailsService matterDetailsService;
private readonly NavigationManager navigationManager;
public bool EditMode { get; private set; } = false;
public int EditMatterId { get; private set; } = 0;
public Matter Matter { get; set; } = new();
public MatterPaymentOptionDetails PaymentDetails { get; set; } = new();
public List<MatterStatus> MatterStatuses { get; private set; } = new();
public MatterDetailsState(
IAppState appState,
IMatterDetailsService matterDetailsService,
NavigationManager navigationManager)
{
this.matterDetailsService = matterDetailsService;
this.navigationManager = navigationManager;
}
public async Task LoadMatterDetails()
{
// Query Params handling
var uri = navigationManager.ToAbsoluteUri(navigationManager.Uri);
var decryptedUri = HelperFunctions.Decrypt(uri.Query);
var queryParamFound = QueryHelpers.ParseQuery(decryptedUri).TryGetValue("MatterID", out StringValues uriMatterID);
if (queryParamFound)
{
EditMatterId = Convert.ToInt32(uriMatterID);
EditMode = !String.IsNullOrEmpty(uriMatterID) && EditMatterId > 0;
}
await LoadMatterStatuses();
if (EditMode)
{
Matter = await matterDetailsService.GetMatterByIdAsync(EditMatterId);
PaymentDetails = await matterDetailsService.GetMatterPaymentInfoByMatterId(EditMatterId);
}
}
private async Task LoadMatterStatuses()
{
MatterStatuses = await matterDetailsService.GetAvailableMatterStatusesAsync();
}
}
По сути, должен ли я вместо того, чтобы иметь более или менее всю функцию в объекте State, или только делать вызовы, такие как установка Matter и PaymentDetails, через функции в объекте State? Не уверен, что стандарт для этого.
2 ответа
Я использовал Fluxor, библиотеку Flux/Redux для Blazor, и она мне понравилась. Он хранит все ваше состояние в объекте, который вы можете внедрить в свой компонент для доступа для чтения. Затем вы управляете состоянием, отправляя действия из ваших компонентов, которые обрабатываются эффектами или редюсерами, которые по сути являются методами, которые обрабатывают действие и вносят изменения в состояние. Он держит все аккуратно, раздельно и очень проверяемо по моему опыту.
«Стандарта» не существует, но применение хороших практик кодирования, таких как «принцип единой ответственности» и принципы чистого дизайна, ведет вас в определенном направлении.
Я делю презентацию и код пользовательского интерфейса на три части:
- UI — компоненты и логика UI
- Состояние — данные, состояние которых вы хотите отслеживать.
- Управление данными - получение, сохранение,....
Каждый представлен одним или несколькими объектами (управление данными — это ViewModel в MVVM).
Вы можете увидеть пример этого в этом ответе - https://stackoverflow.com/a/75157903/13065781
Проблема заключается в том, как создать экземпляр ViewModel с той же областью действия, что и компонент Form. Вы либо:
Назовите виртуальную машину временной — вы можете каскадировать ее в форме, если подкомпонентам требуется прямой доступ к ней. Это подход в указанном примере.
Создайте экземпляр из использования
ActivatorUtilities
и разобраться с утилизацией в компоненте формы.
Если виртуальная машина реализуетIDisposable/IAsycDisposable
вы должны сделать второй.
Следующий класс расширения добавляет два метода кIServiceProvider
которые завершают эту функциональность.
public static class ServiceUtilities
{
public static bool TryGetComponentService<TService>(this IServiceProvider serviceProvider,[NotNullWhen(true)] out TService? service) where TService : class
{
service = serviceProvider.GetComponentService<TService>();
return service != null;
}
public static TService? GetComponentService<TService>(this IServiceProvider serviceProvider) where TService : class
{
var serviceType = serviceProvider.GetService<TService>()?.GetType();
if (serviceType is null)
return ActivatorUtilities.CreateInstance<TService>(serviceProvider);
return ActivatorUtilities.CreateInstance(serviceProvider, serviceType) as TService;
}
}
Тогда ваша форма может выглядеть примерно так:
public partial class UIForm: UIWrapperBase, IAsyncDisposable
{
[Inject] protected IServiceProvider ServiceProvider { get; set; } = default!;
public MyEditorPresenter Presenter { get; set; } = default!;
private IDisposable? _disposable;
public override Task SetParametersAsync(ParameterView parameters)
{
// overries the base as we need to make sure we set up the Presenter Service before any rendering takes place
parameters.SetParameterProperties(this);
if (!initialized)
{
// Gets an instance of the Presenter from the Service Provider
this.Presenter = ServiceProvider.GetComponentService<MyEditorPresenter>() ?? default!;
if (this.Presenter is null)
throw new NullReferenceException($"No Presenter could be created.");
_disposable = this.Presenter as IDisposable;
}
return base.SetParametersAsync(ParameterView.Empty);
}
//....
public async ValueTask DisposeAsync()
{
_disposable?.Dispose();
if (this.Presenter is IAsyncDisposable asyncDisposable)
await asyncDisposable.DisposeAsync();
}
}