ASP.NET Core Blazor 核心功能一:Blazor依賴注入與狀態管理指南
大家好,我是碼農剛子。本文詳細介紹了Blazor框架中的依賴注入機制和狀態管理方案。依賴注入部分闡述了服務注冊的三種生命周期方式(Singleton/Scoped/Transient)及在組件中的使用方法。狀態管理章節系統梳理了7種解決方案:從簡單的組件內狀態到父子組件通信、級聯參數,再到全局狀態容器和Flux/Redux模式,并提供了本地存儲持久化方案。文章還介紹了@ref指令的使用場景,包括組件引用、元素操作和循環處理等。最后給出了不同場景下的狀態管理選擇建議,幫助開發者構建更健壯。
一、依賴注入基礎
Blazor 提供了強大的依賴注入(Dependency Injection, DI)功能,用于將服務以解耦的方式注入到組件中,它幫助我們實現松耦合的代碼設計,提高可測試性和可維護性。
什么是依賴注入?
依賴注入是一種設計模式,它允許類從外部接收其依賴項,而不是自己創建它們。在 Blazor 中,這意味著組件不需要知道如何創建服務,而是通過構造函數或屬性接收這些服務。
二、注冊和使用服務
1、創建自定義服務
1. 定義服務接口
public interface ICounterService { int Increment(int currentValue); int Decrement(int currentValue); void Reset(); }
2. 實現服務
public class CounterService : ICounterService { public int Increment(int currentValue) { return currentValue + 1; } public int Decrement(int currentValue) { return currentValue - 1; } public void Reset() { // 重置邏輯 } }
2、注冊服務
在 Program.cs 文件中配置服務容器:
var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("#app"); // 注冊服務 builder.Services.AddSingleton<ICounterService, CounterService>(); builder.Services.AddScoped<IUserService, UserService>(); builder.Services.AddTransient<IEmailService, EmailService>(); // 注冊內置服務 builder.Services.AddLocalStorage(); builder.Services.AddAuthorizationCore(); await builder.Build().RunAsync();
3、服務生命周期
Blazor 支持三種服務生命周期:
Singleton:整個應用生命周期內只有一個實例Scoped:每個用戶會話有一個實例(Blazor Server)或每個瀏覽器標簽頁(Blazor WebAssembly)Transient:每次請求時創建新實例
4、在組件中使用依賴注入
1. 使用 [Inject] 特性
@page "/counter" @inject ICounterService CounterService <h3>Counter</h3> <p>Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int currentCount = 0; private void IncrementCount() { currentCount = CounterService.Increment(currentCount); } }
2. 在代碼中使用注入的服務
@page "/user-profile" @inject IUserService UserService @inject NavigationManager Navigation <h3>User Profile</h3> @if (user != null) { <div> <p>Name: @user.Name</p> <p>Email: @user.Email</p> </div> } @code { private User user; protected override async Task OnInitializedAsync() { user = await UserService.GetCurrentUserAsync(); } private async Task UpdateProfile() { await UserService.UpdateUserAsync(user); Navigation.NavigateTo("/success"); } }
5、高級依賴注入用法
1. 工廠模式注冊
builder.Services.AddSingleton<ICounterService>(provider => { // 復雜的創建邏輯 return new CounterService(); });
2. 選項模式
// 配置選項類 public class ApiOptions { public string BaseUrl { get; set; } public int TimeoutSeconds { get; set; } } // 注冊選項 builder.Services.Configure<ApiOptions>(options => { options.BaseUrl = "https://api.example.com"; options.TimeoutSeconds = 30; }); // 在服務中使用 public class ApiService { private readonly ApiOptions _options; public ApiService(IOptions<ApiOptions> options) { _options = options.Value; } }
3. 條件注冊
#if DEBUG builder.Services.AddSingleton<ILogger, DebugLogger>(); #else builder.Services.AddSingleton<ILogger, ProductionLogger>(); #endif
三、組件狀態管理
在Blazor開發中,狀態管理是構建交互式Web應用的核心挑戰。無論是簡單的計數器組件還是復雜的實時協作系統,選擇合適的狀態管理方案直接影響應用性能和可維護性。
1、理解Blazor中的狀態管理
- 狀態是指應用程序或組件在某一時刻的數據或信息。例如,一個計數器組件可以有一個表示當前計數值的狀態,一個表單組件可以有一個表示用戶輸入的狀態,一個購物車組件可以有一個表示選中商品的狀態等。狀態管理是指如何存儲、更新、獲取和傳遞這些數據或信息。
- 在Blazor中,每個組件都有自己的私有狀態,它只能被該組件訪問和修改。如果要將狀態從一個組件傳遞給另一個組件,或者在多個組件之間共享狀態,就需要使用一些技術或模式來實現。下面我們將介紹一些常見的方法。
2、組件內狀態:最簡單的狀態管理
Blazor組件最基礎的狀態管理方式是使用組件內部的字段或屬性保存狀態。這種模式適用于狀態僅在單個組件內部使用且無需共享的場景,如計數器、表單輸入等基礎交互。
@page "/counter" <h1>Counter</h1> <p>Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int currentCount = 0; private void IncrementCount() { currentCount++; } }
上述代碼展示了典型的組件內狀態模式,currentCount字段存儲計數器狀態,IncrementCount方法修改狀態并自動觸發UI重新渲染。這種模式的優勢在于實現簡單、零外部依賴,適合快速開發獨立功能組件。
3、父子組件通信:參數和事件回調
如果要將父組件的狀態傳遞給子組件,或者從子組件獲取更新后的狀態,可以使用參數和屬性來實現。
參數是指父組件向子組件傳遞數據或信息的方式。參數可以是任意類型的值,例如字符串、數字、布爾值、對象、委托等。要定義一個參數,需要在子組件中使用[Parameter]特性來標記一個公共屬性,并且該屬性的類型必須與父組件傳遞的值相同。例如:
這樣就定義了一個名為Counter的參數,在子組件中可以使用以下語法來獲取它的值:
<p>The counter value is @Counter</p>
在父組件中,可以使用以下語法來為參數賦值:
<CounterComponent Counter="@currentCount" /> @code { private int currentCount = 0; }
這樣就將父組件中的變量currentCount作為參數值傳遞給了子組件。如果要實現從父到子單向綁定。
屬性是指子組件向父組件傳遞數據或信息的方式。屬性可以是任意類型的值,但通常是一個事件回調(EventCallback)或一個動作(Action),用于在子組件中觸發父組件定義的一個方法,從而將數據或信息傳遞給父組件。要定義一個屬性,需要在子組件中使用[Parameter]特性來標記一個公共屬性,并且該屬性的類型必須是EventCallback<T>或Action<T>,其中T是要傳遞的數據或信息的類型。例如:
<h3>CounterComponent</h3> <p>The counter value is @Counter</p> <button @onclick="CounterChangedFromChild">Update Counter from Child</button> @code { [Parameter] public int Counter { get; set; } [Parameter] public EventCallback<int> OnCounterChanged { get; set; } private async Task CounterChangedFromChild() { Counter++; await OnCounterChanged.InvokeAsync(Counter); } }
以上例子中就定義了一個名為OnCounterChanged的屬性,將子組件中的變量Counter作為參數傳遞給了父組件。在父組件中,可以使用以下語法來為屬性賦值:
<CounterComponent OnCounterChanged="HandleCounterChanged" />
這樣就將父組件中定義的一個方法名作為屬性值傳遞給了子組件。該方法必須接受一個與屬性類型相同的參數,并且可以在其中處理數據或信息。例如:
@code{ private void HandleCounterChanged(int counter) { Console.WriteLine($"The counter value is {counter}"); } }
這樣就實現了從子到父單向傳遞數據或信息,并且可以在任何時候觸發。
使用組件參數和屬性傳遞狀態:適合父子組件之間的簡單狀態傳遞,可以使用[Parameter]或者級聯參數[CascadingParameter]特性來標記組件參數,并且使用<Component Parameter="Value" />或者<CascadingValue Value="Value"><Component /></CascadingValue>語法來傳遞狀態。
4、級聯參數和值
<!-- AppStateProvider.razor --> <CascadingValue Value="this"> @ChildContent </CascadingValue> @code { [Parameter] public RenderFragment? ChildContent { get; set; } private string theme = "light"; public string Theme { get => theme; set { if (theme != value) { theme = value; StateHasChanged(); } } } public event Action? OnThemeChanged; public void ToggleTheme() { Theme = Theme == "light" ? "dark" : "light"; OnThemeChanged?.Invoke(); } }
<!-- ConsumerComponent.razor --> <div class="@($"app-{appState.Theme}")"> <h3>當前主題: @appState.Theme</h3> <button @onclick="appState.ToggleTheme">切換主題</button> </div> @code { [CascadingParameter] public AppStateProvider appState { get; set; } = default!; protected override void OnInitialized() { if (appState != null) { appState.OnThemeChanged += StateHasChanged; } } public void Dispose() { if (appState != null) { appState.OnThemeChanged -= StateHasChanged; } } }
5、狀態容器模式(全局狀態)
創建狀態容器服務
// Services/AppState.cs public class AppState { private int _counter; private string _userName = string.Empty; public int Counter { get => _counter; set { _counter = value; OnCounterChanged?.Invoke(); } } public string UserName { get => _userName; set { _userName = value; OnUserNameChanged?.Invoke(); } } public event Action? OnCounterChanged; public event Action? OnUserNameChanged; public void IncrementCounter() { Counter++; } }
注冊服務
// Program.cs builder.Services.AddScoped<AppState>();
在組件中使用
@inject AppState AppState @implements IDisposable <h3>計數器: @AppState.Counter</h3> <h4>用戶: @AppState.UserName</h4> <button @onclick="AppState.IncrementCounter">增加計數</button> <input @bind="localUserName" @bind:event="onchange" placeholder="輸入用戶名" /> @code { private string localUserName { get => AppState.UserName; set { AppState.UserName = value; // 可以在這里添加其他邏輯 } } protected override void OnInitialized() { AppState.OnCounterChanged += StateHasChanged; AppState.OnUserNameChanged += StateHasChanged; } public void Dispose() { AppState.OnCounterChanged -= StateHasChanged; AppState.OnUserNameChanged -= StateHasChanged; } }
6、Flux/Redux 模式
什么是Flux模式?
Flux是一種應用程序架構模式,專門用于管理前端應用中的狀態。與常見的MVC模式不同,Flux采用單向數據流的設計,使得狀態變化更加可預測和易于追蹤。
Flux模式的核心思想是將狀態管理與UI渲染分離,通過嚴格的規則來規范狀態變更的過程。這種模式最初由Facebook提出,后來被Redux等庫實現,而Fluxor則是專門為Blazor應用設計的實現方案。
Flux模式的核心原則
- 狀態只讀原則
應用的狀態在任何情況下都不應該被直接修改,這保證了狀態變更的可控性。
- 動作驅動變更
任何狀態變更都必須通過派發(dispatch)一個動作(action)來觸發。動作是一個簡單的對象,描述了發生了什么變化。
- 純函數處理
使用稱為"reducer"的純函數來處理動作,根據當前狀態和動作生成新狀態。Reducer不會修改原有狀態,而是返回全新的狀態對象。
- 單向數據流
UI組件訂閱狀態變化,當狀態更新時自動重新渲染。用戶交互則通過派發動作來觸發狀態變更,形成完整的單向循環。
核心概念
- ?狀態(State)?:定義應用數據模型,不可直接修改,需通過動作(Action)觸發更新。
- ?動作(Action)?:描述狀態變更意圖的對象,包含類型標識和有效載荷。
- ?歸約器(Reducer)?:純函數,根據當前狀態和動作生成新狀態。
- ?效果(Effect)?:處理副作用操作(如 API 調用),監聽動作并執行異步任務。
- 中間件(Middleware):中間件可以在動作被派發到reducer之前或之后執行自定義邏輯,用于日志記錄、性能監控等橫切關注點。
使用 Fluxor 庫
首先安裝 Fluxor:
Install-Package Fluxor.Blazor.Web
定義狀態和動作
// Store/CounterState.cs public record CounterState { public int Count { get; init; } } // Store/Actions/IncrementCounterAction.cs public record IncrementCounterAction; // Store/Reducers/CounterReducers.cs public static class CounterReducers { [ReducerMethod] public static CounterState OnIncrementCounter(CounterState state, IncrementCounterAction action) { return state with { Count = state.Count + 1 }; } }
在組件中使用
@using Fluxor @inherits Fluxor.Blazor.Web.Components.FluxorComponent <h3>計數器: @State.Value.Count</h3> <button @onclick="Increment">增加</button> @code { [Inject] private IState<CounterState> State { get; set; } = null!; [Inject] private IDispatcher Dispatcher { get; set; } = null!; private void Increment() { Dispatcher.Dispatch(new IncrementCounterAction()); } }
7、本地存儲持久化
使用 Blazor 本地存儲
@page "/counter2" @inject IJSRuntime JSRuntime <h3>持久化計數器: @count</h3> <button @onclick="Increment">增加并保存</button> @code { private int count = 0; private bool isInitialized = false; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await LoadFromStorage(); isInitialized = true; StateHasChanged(); // 確保在加載后更新UI } } private async Task Increment() { count++; await SaveToStorage(); //StateHasChanged(); } private async Task SaveToStorage() { if (isInitialized) { await JSRuntime.InvokeVoidAsync("localStorage.setItem", "counter", count); } } private async Task LoadFromStorage() { try { var savedCount = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "counter"); if (int.TryParse(savedCount, out int result)) { count = result; } } catch (Exception ex) { // 處理預渲染期間的 JS 互操作錯誤 Console.WriteLine($"加載存儲時出錯: {ex.Message}"); } } }
8、狀態管理選擇指南

四、使用 @ref 引用組件
在 Blazor 中,@ref 指令用于獲取對組件或 HTML 元素的引用,讓你能夠在代碼中直接操作它們。以下是詳細的使用方法:
1、引用組件
基本用法
<!-- MyComponent.razor --> <h3>計數器: @count</h3> <button @onclick="Increment">增加</button> @code { private int count = 0; public void Increment() { count++; StateHasChanged(); } public void Reset() { count = 0; StateHasChanged(); } }
<!-- ParentComponent.razor --> @page "/parent" <MyComponent @ref="myComponentRef" /> <button @onclick="ResetChild">重置子組件</button> @code { private MyComponent? myComponentRef; private void ResetChild() { myComponentRef?.Reset(); } }
2、引用 HTML 元素
@page "/element-ref" <input @ref="usernameInput" placeholder="輸入用戶名" /> <button @onclick="FocusInput">聚焦輸入框</button> @code { private ElementReference usernameInput; private async Task FocusInput() { await usernameInput.FocusAsync(); } }
3、在循環中使用 @ref
@page "/loop-ref-example" <h3>循環中使用 ref 示例</h3> <button @onclick="ShowAllMessages" class="btn btn-primary">顯示所有消息</button> <button @onclick="UpdateAllMessages" class="btn btn-secondary">更新所有消息</button> @foreach (var item in items) { <ChildComponent @ref="componentRefs[item.Id]" Id="item.Id" Message="@item.Message" OnMessageChanged="HandleMessageChanged" /> } @code { private List<ItemModel> items = new(); private Dictionary<int, ChildComponent?> componentRefs = new(); protected override void OnInitialized() { items = new List<ItemModel> { new ItemModel { Id = 1, Message = "第一條消息" }, new ItemModel { Id = 2, Message = "第二條消息" }, new ItemModel { Id = 3, Message = "第三條消息" } }; // 預先初始化字典 foreach (var item in items) { componentRefs[item.Id] = null; } } private void ShowAllMessages() { foreach (var component in componentRefs.Values) { component?.ShowCurrentMessage(); } } private void UpdateAllMessages() { foreach (var item in items) { if (componentRefs.TryGetValue(item.Id, out var component) && component != null) { component.UpdateMessage($"更新后的消息 {item.Id}"); } } } private void HandleMessageChanged((int Id, string Message) data) { Console.WriteLine($"收到消息更新 - ID: {data.Id}, 消息: {data.Message}"); var item = items.FirstOrDefault(i => i.Id == data.Id); if (item != null) { item.Message = data.Message; StateHasChanged(); } } public class ItemModel { public int Id { get; set; } public string Message { get; set; } = string.Empty; } }
<!-- ChildComponent.razor --> <div class="child-component"> <h5>子組件 ID: @Id</h5> <p>當前消息: <strong>@Message</strong></p> <input @bind="currentMessage" @bind:event="oninput" /> <button @onclick="UpdateMessage" class="btn btn-sm btn-info">更新消息</button> </div> @code { [Parameter] public int Id { get; set; } [Parameter] public string Message { get; set; } = string.Empty; [Parameter] public EventCallback<(int Id, string Message)> OnMessageChanged { get; set; } private string currentMessage = string.Empty; protected override void OnParametersSet() { currentMessage = Message; } private async Task UpdateMessage() { await OnMessageChanged.InvokeAsync((Id, currentMessage)); } public void ShowCurrentMessage() { Console.WriteLine($"組件 {Id} 的消息: {Message}"); } public void UpdateMessage(string newMessage) { currentMessage = newMessage; UpdateMessage().Wait(); } }
4、使用 ref 回調
<CustomInput @ref="SetInputRef" /> @code { private CustomInput? inputRef; private void SetInputRef(CustomInput component) { inputRef = component; // 組件引用設置后的初始化邏輯 component?.Initialize(); } }
5、與 JavaScript 互操作
@inject IJSRuntime JSRuntime <div @ref="myDiv" style="width: 100px; height: 100px; background: red;"></div> <button @onclick="ChangeDivStyle">修改樣式</button> @code { private ElementReference myDiv; private async Task ChangeDivStyle() { await JSRuntime.InvokeVoidAsync("changeElementStyle", myDiv); } }
對應的 JavaScript 文件:
// wwwroot/js/site.js window.changeElementStyle = (element) => { element.style.background = 'blue'; element.style.width = '200px'; };
本章節中用到:IJSRuntime ,后面會詳細講解。
以上就是關于《ASP.NET Core Blazor 核心功能一:Blazor依賴注入與狀態管理指南》的全部內容,希望你有所收獲。關注我,持續分享。

浙公網安備 33010602011771號