组件通信与 DI
在父组件和子组件之间传递数据,使用级联参数,并将服务注入组件。
组件通信与 DI 是 CoddyKit 上的免费 C# Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
组件通信模式
Blazor 组件通过三个方向进行通信:父组件到子组件(参数)、子组件到父组件(EventCallback),以及跨组件树(CascadingParameters 或通过 DI 共享服务)。
父组件到子组件:参数
为组件属性添加 [Parameter] 特性,使其可以由父组件设置。父组件会将值作为组件标签上的 HTML 属性传入。
@* Child: Alert.razor *@
<div class="alert alert-@Type">
@Message
</div>
@code {
[Parameter] public string Type { get; set; } = "info"; // primary, warning, danger
[Parameter, EditorRequired] public string Message { get; set; } = "";
}
@* Parent usage: *@
<Alert Type="danger" Message="Something went wrong!" />
<Alert Message="Record saved." />子组件到父组件:EventCallback
子组件公开一个 EventCallback 参数。父组件提供一个处理程序方法。当某个事件发生时,子组件调用该方法。
@* Child: QuantityPicker.razor *@
<button @onclick="() => OnChanged.InvokeAsync(Value - 1)">-</button>
<span>@Value</span>
<button @onclick="() => OnChanged.InvokeAsync(Value + 1)">+</button>
@code {
[Parameter] public int Value { get; set; } = 1;
[Parameter] public EventCallback<int> OnChanged { get; set; }
}
@* Parent: *@
<QuantityPicker Value="@_qty" OnChanged="q => _qty = q" />
<p>Quantity: @_qty</p>通过父组件进行兄弟组件通信
要让兄弟组件进行通信,请通过共同的父组件传递数据。父组件持有共享状态,并将其作为参数传递给子组件。
@* Parent holds shared state *@
<CategoryFilter OnChanged="c => _category = c" />
<ProductList Category="_category" />
@code {
private string _category = "All";
}
@* CategoryFilter raises an event -> Parent updates _category
-> ProductList re-renders with new Category *@级联参数
使用 <CascadingValue> 包裹一个子树,即可将数据传递给所有后代组件,而不必让数据经过每个中间组件。
@* Wrap a subtree *@
<CascadingValue Value="_theme">
<MainLayout />
</CascadingValue>
@code { private Theme _theme = new Theme { PrimaryColor = "#6366f1" }; }
@* Any descendant can receive it: *@
@code {
[CascadingParameter]
public Theme AppTheme { get; set; } = default!;
}
<h1 style="color: @AppTheme.PrimaryColor">Hello</h1>使用 @inject 注入服务
使用 @inject 指令,将已注册的 DI 服务直接注入组件。框架会自动解析这些服务。
@inject IProductService ProductSvc
@inject ILogger<ProductList> Logger
@inject NavigationManager Nav
@code {
private List<Product> _products = new();
protected override async Task OnInitializedAsync()
{
Logger.LogInformation("Loading products");
_products = await ProductSvc.GetAllAsync();
}
private void GoToProduct(int id)
=> Nav.NavigateTo($"/products/{id}");
}Blazor Server 中的作用域服务
在 Blazor Server 中,DI 作用域会持续整个 SignalR 连接的生命周期,而不仅仅是一次请求。请注意,在同一个电路中,所有组件共享作用域服务。
// Blazor Server: scoped = per circuit (connection lifetime)
builder.Services.AddScoped<CartService>();
// All components in the same browser tab share the SAME CartService
// This is GOOD for cart state (user's items persist across pages)
// This is BAD for request-scoped data — treat as session-like scope
// For true per-render-cycle scope, create your own:
using var scope = ScopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();使用 NavigationManager 进行路由
注入 NavigationManager,即可通过程序导航、读取当前 URL 并处理导航事件。
@inject NavigationManager Nav
@code {
private void GoHome() => Nav.NavigateTo("/");
private void GoToProduct(int id) => Nav.NavigateTo($"/products/{id}");
private void ForceReload() => Nav.NavigateTo("/", forceLoad: true);
protected override void OnInitialized()
{
// Read current URL
var uri = Nav.Uri;
var path = new Uri(uri).AbsolutePath;
}
}Blazor WASM 中的 HttpClient
在 Blazor WebAssembly 中,注入预先配置好的 HttpClient 来调用您的 API。在 Program.cs 中使用基地址注册它。
// Program.cs (Blazor WASM)
builder.Services.AddScoped(sp => new HttpClient
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});
// Component:
@inject HttpClient Http
@code {
private List<Product> _products = new();
protected override async Task OnInitializedAsync()
{
_products = await Http.GetFromJsonAsync<List<Product>>("/api/products")
?? new();
}
}实际应用:用户上下文服务
在应用的多个组件中注入作用域 UserContextService,这样每个组件都能看到相同的用户数据,而无需逐层传递属性。
public class UserContextService
{
public string? Name { get; private set; }
public bool IsAdmin { get; private set; }
public async Task InitializeAsync(AuthenticationState auth)
{
var user = auth.User;
Name = user.Identity?.Name;
IsAdmin = user.IsInRole("Admin");
}
}
// Registered as scoped:
builder.Services.AddScoped<UserContextService>();
// Used in any component:
@inject UserContextService UserCtx
<p>Hello, @UserCtx.Name @(UserCtx.IsAdmin ? "(Admin)" : "")</p>快速检查
在 Blazor Server 中,一个“作用域”DI 服务的生命周期会持续多久?
回顾:组件通信与 DI
要点:
- 父组件 → 子组件:使用 [参数] 属性;使用 [EditorRequired] 表示必需参数
- 子组件 → 父组件:使用 EventCallback;会自动触发父组件的 StateHasChanged
- 兄弟组件:通过父组件状态传递,或使用共享 DI 服务
- 跨组件树:使用 CascadingValue/CascadingParameter 处理深层子树
- 使用 @inject 注入服务;Blazor Server 的作用域服务持续整个电路生命周期
- 使用 NavigationManager 进行程序化路由
常见问题解答
「组件通信与 DI」课时是免费的吗?
是的 — 「组件通信与 DI」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「组件通信与 DI」这节课中我会学到什么?
在父组件和子组件之间传递数据,使用级联参数,并将服务注入组件。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「组件通信与 DI」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。