Blazorの状態管理
サービスのシングルトン、Fluxor、またはカスケーディングするAppStateで共有状態を管理し、コンポーネント間でUIの一貫性を保ちます。
「Blazorの状態管理」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
Blazor における状態管理の課題
Blazor アプリが大きくなると、関連のない多くのコンポーネント間で共有する状態の管理は複雑になります。単純な prop drilling では対応しきれないため、集中管理されたリアクティブな状態ストアが必要です。
Scoped サービスによるシンプルな状態管理
Blazor で最もシンプルな状態管理方法は、状態を保持し、コンポーネントが変更を購読できるイベントを公開する Scoped サービスです。
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 で囲みます。すべての子孫が、明示的に prop を受け渡さなくても状態サービスにアクセスできます。
@* 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:Blazor の Flux パターン
Fluxor は、Redux スタイルの単方向データフローを Blazor に導入します。State → Component → Action → Reducer → State という流れで、複雑なアプリケーションに適しています。
// 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>(または mixin)を使用すると、状態変更の購読が自動的に行われます。
@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 への永続化
JavaScript 相互運用を介してブラウザーの localStorage を使用すると、ページを更新しても状態を保持できます。アプリの起動時に状態を復元してください。
@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 パッケージは、JavaScript 相互運用の呼び出しを自分で記述しなくても、ブラウザーの localStorage を扱える、使いやすい型付き API を提供します。
// 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();
}NavigationManager による URL 状態管理
ページ更新後も維持し、ブックマークできる状態には、クエリパラメーターを使用します。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
}));
}
}実践例:ショッピングカートの状態管理
完全なパターンでは、イベントを持つ Scoped な 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 の状態管理
重要なポイント:
- Scoped サービス + Action イベント:最もシンプルな共有状態管理で、多くのアプリに適している
- ルートを CascadingValue で囲むと、prop drilling なしでアプリ全体からアクセスできる
- Fluxor:複雑な状態ロジック向けの Redux スタイルの単方向フロー
- IJSRuntime または Blazored.LocalStorage を使用して状態を localStorage に永続化する
- URL 状態(SupplyParameterFromQuery):ブックマークや共有が可能な UI 状態に使用する
- メモリリークを防ぐため、Dispose では必ずイベントの購読を解除する
よくある質問
「Blazorの状態管理」レッスンは無料ですか?
はい。「Blazorの状態管理」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「Blazorの状態管理」で何を学びますか?
サービスのシングルトン、Fluxor、またはカスケーディングするAppStateで共有状態を管理し、コンポーネント間でUIの一貫性を保ちます。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「Blazorの状態管理」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。