Как реализовать шаблон состояния для страниц 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, и она мне понравилась. Он хранит все ваше состояние в объекте, который вы можете внедрить в свой компонент для доступа для чтения. Затем вы управляете состоянием, отправляя действия из ваших компонентов, которые обрабатываются эффектами или редюсерами, которые по сути являются методами, которые обрабатывают действие и вносят изменения в состояние. Он держит все аккуратно, раздельно и очень проверяемо по моему опыту.

https://github.com/mrpmorris/Флюксор

«Стандарта» не существует, но применение хороших практик кодирования, таких как «принцип единой ответственности» и принципы чистого дизайна, ведет вас в определенном направлении.

Я делю презентацию и код пользовательского интерфейса на три части:

  1. UI — компоненты и логика UI
  2. Состояние — данные, состояние которых вы хотите отслеживать.
  3. Управление данными - получение, сохранение,....

Каждый представлен одним или несколькими объектами (управление данными — это ViewModel в MVVM).

Вы можете увидеть пример этого в этом ответе - https://stackoverflow.com/a/75157903/13065781

Проблема заключается в том, как создать экземпляр ViewModel с той же областью действия, что и компонент Form. Вы либо:

  1. Назовите виртуальную машину временной — вы можете каскадировать ее в форме, если подкомпонентам требуется прямой доступ к ней. Это подход в указанном примере.

  2. Создайте экземпляр из использования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();
    }
}

Другие вопросы по тегам