Управление состоянием в Blazor
Управляйте общим состоянием с помощью одиночных экземпляров служб, Fluxor или каскадного AppState, сохраняя согласованность интерфейса между компонентами.
«Управление состоянием в Blazor» — бесплатный урок C# Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.
Сложности управления состоянием в Blazor
По мере роста приложений Blazor управление состоянием, которое нужно совместно использовать во множестве не связанных друг с другом компонентов, становится сложным. Простая передача параметров через цепочку компонентов перестаёт работать; требуется централизованное реактивное хранилище состояния.
Простое состояние с помощью службы с областью действия
Самый простой способ управлять состоянием в Blazor — служба с областью действия, которая хранит состояние и предоставляет событие, чтобы компоненты могли подписываться на изменения.
public class CartState
{
private readonly List<CartItem> _items = new();
public IReadOnlyList<CartItem> Items => _items;
public int Count => _items.Sum(i => i.Quantity);
public event Action? OnChanged;
public void Add(Product p)
{
var existing = _items.FirstOrDefault(i => i.ProductId == p.Id);
if (existing is not null) existing.Quantity++;
else _items.Add(new CartItem { ProductId = p.Id, Name = p.Name, Price = p.Price });
OnChanged?.Invoke();
}
}Подписка на изменения состояния
Компоненты подписываются на событие OnChanged службы состояния и вызывают StateHasChanged, чтобы перерисоваться. Не забудьте отменить подписку в Dispose.
@inject CartState Cart
@implements IDisposable
<p>Cart: @Cart.Count items</p>
@code {
protected override void OnInitialized()
{
Cart.OnChanged += OnCartChanged;
}
private void OnCartChanged()
{
InvokeAsync(StateHasChanged);
}
public void Dispose() =>
Cart.OnChanged -= OnCartChanged;
}CascadingValue как контейнер состояния
Оберните корневой макет в CascadingValue, передав ему службу состояния. Все потомки смогут обращаться к ней без явной передачи параметров через цепочку компонентов.
@* MainLayout.razor *@
@inject CartState Cart
<CascadingValue Value="Cart">
@Body
</CascadingValue>
@* Any nested component: *@
@code {
[CascadingParameter]
public CartState Cart { get; set; } = default!;
private void Buy(Product p) => Cart.Add(p);
}Fluxor: шаблон Flux для Blazor
Fluxor добавляет в Blazor однонаправленный поток данных в стиле Redux: состояние → компонент → действие → редуктор → состояние. Это особенно удобно для сложных приложений.
// dotnet add package Fluxor.Blazor.Web
// State record
[FeatureState]
public record CounterState(int Count = 0);
// Actions
public record IncrementAction(int Amount = 1);
// Reducers
public static class CounterReducers
{
[ReducerMethod]
public static CounterState Reduce(CounterState state, IncrementAction action)
=> state with { Count = state.Count + action.Amount };
}
// Registration:
builder.Services.AddFluxor(opt =>
opt.ScanAssemblies(typeof(Program).Assembly));Использование Fluxor в компоненте
Внедрите IState<T> для чтения состояния и IDispatcher для отправки действий. InheritsFrom<FluxorComponent> (или примесь) автоматически подписывается на изменения состояния.
@inherits FluxorComponent
@inject IState<CounterState> CounterState
@inject IDispatcher Dispatcher
<p>Count: @CounterState.Value.Count</p>
<button @onclick="Increment">+1</button>
<button @onclick="() => Dispatcher.Dispatch(new IncrementAction(5))">+5</button>
@code {
private void Increment()
=> Dispatcher.Dispatch(new IncrementAction(1));
}Сохранение состояния в LocalStorage
Сохраняйте состояние между обновлениями страницы в браузерном localStorage с помощью взаимодействия с JavaScript. Восстанавливайте его при запуске приложения.
@inject IJSRuntime JS
@code {
private async Task SaveStateAsync()
{
var json = JsonSerializer.Serialize(_state);
await JS.InvokeVoidAsync("localStorage.setItem", "appState", json);
}
protected override async Task OnInitializedAsync()
{
var json = await JS.InvokeAsync<string?>("localStorage.getItem", "appState");
if (json is not null)
_state = JsonSerializer.Deserialize<AppState>(json) ?? new();
}
private AppState _state = new();
}Пакет Blazored.LocalStorage
Пакет Blazored.LocalStorage предоставляет удобный типизированный API для браузерного localStorage, избавляя от необходимости вручную писать вызовы взаимодействия с JS.
// dotnet add package Blazored.LocalStorage
builder.Services.AddBlazoredLocalStorage();
@inject ILocalStorageService LocalStorage
@code {
protected override async Task OnInitializedAsync()
{
_cart = await LocalStorage.GetItemAsync<List<CartItem>>("cart") ?? new();
}
private async Task SaveCartAsync()
{
await LocalStorage.SetItemAsync("cart", _cart);
}
private List<CartItem> _cart = new();
}Состояние URL с помощью NavigationManager
Используйте параметры запроса для хранения состояния, которое должно сохраняться после обновления страницы и поддерживать добавление в закладки. Читайте и записывайте их с помощью NavigationManager и конструктора URI.
@inject NavigationManager Nav
@page "/products"
@code {
[SupplyParameterFromQuery] public string? Search { get; set; }
[SupplyParameterFromQuery] public int Page { get; set; } = 1;
private void UpdateSearch(string s)
{
Nav.NavigateTo(
Nav.GetUriWithQueryParameters(
new Dictionary<string, object?>
{
[nameof(Search)] = s,
[nameof(Page)] = 1
}));
}
}Практический пример: состояние корзины покупок
Полный шаблон: служба с областью действия CartState и событиями, сохраняющая состояние в localStorage при изменении и восстанавливающая его при загрузке.
public class CartState
{
private readonly ILocalStorageService _storage;
private List<CartItem> _items = new();
public event Action? OnChanged;
public CartState(ILocalStorageService storage) => _storage = storage;
public async Task InitAsync()
{
_items = await _storage.GetItemAsync<List<CartItem>>("cart") ?? new();
}
public async Task AddAsync(Product p)
{
var item = _items.FirstOrDefault(i => i.ProductId == p.Id);
if (item is not null) item.Quantity++;
else _items.Add(new CartItem { ProductId = p.Id, Name = p.Name });
await _storage.SetItemAsync("cart", _items);
OnChanged?.Invoke();
}
}Быстрая проверка
Какой подход к управлению состоянием лучше всего подходит для простого приложения Blazor с небольшим объёмом общего состояния?
Итоги: управление состоянием в Blazor
Основные выводы:
- Служба с областью действия + событие действия: простейший вариант общего состояния, подходящий для большинства приложений
- CascadingValue, оборачивающий корневой компонент, обеспечивает доступ во всём приложении без передачи параметров через цепочку компонентов
- Fluxor: однонаправленный поток в стиле Redux для сложной логики состояния
- Сохраняйте состояние в localStorage с помощью IJSRuntime или Blazored.LocalStorage
- Состояние в URL (
SupplyParameterFromQuery) для доступного добавления в закладки и обмена состоянием интерфейса - Всегда отменяйте подписку на события в Dispose, чтобы предотвращать утечки памяти
Часто задаваемые вопросы
Урок «Управление состоянием в Blazor» бесплатный?
Да — полный текст урока «Управление состоянием в Blazor» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.
Чему я научусь в уроке «Управление состоянием в Blazor»?
Управляйте общим состоянием с помощью одиночных экземпляров служб, Fluxor или каскадного AppState, сохраняя согласованность интерфейса между компонентами. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать C# Academy?
Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Управление состоянием в Blazor»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке C# Academy?
Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Модель компонентов Blazor
- Привязка данных и обработка событий
- Взаимодействие компонентов и DI
- Управление состоянием в Blazor