注意
此版本不是本文的最新版本。 有关当前版本,请参阅本文的 .NET 9 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 有关当前版本,请参阅本文的 .NET 9 版本。
本文介绍如何将数据从上级 Razor 组件流向下级组件。
级联值和参数提供了一种方便的方法,可将数据沿组件层次结构从祖先组件向下流向任意数量的后代组件。 不同于组件参数,级联值和参数不需要对使用数据的每个后代组件分配特性。 级联值和参数还允许组件在组件层次结构中相互协调。
注意
本文中的代码示例采用在 .NET 6 或更高版本的 ASP.NET Core 中支持的可为空的引用类型 (NRT) 和 .NET 编译器 Null 状态静态分析。 针对 .NET 5 或更早版本时,请从文章示例中的 CascadingType?
、@ActiveTab?
、RenderFragment?
、ITab?
、TabSet?
和 string?
类型中删除 null 类型指定(?
)。
根级别级联值
可以为整个组件层次结构注册根级别级联值。 支持用于更新通知的命名级联值和订阅。
本部分的示例中使用了以下类。
Dalek.cs
:
// "Dalek" ©Terry Nation https://www.imdb.com/name/nm0622334/
// "Doctor Who" ©BBC https://www.bbc.co.uk/programmes/b006q2x0
namespace BlazorSample;
public class Dalek
{
public int Units { get; set; }
}
// "Dalek" ©Terry Nation https://www.imdb.com/name/nm0622334/
// "Doctor Who" ©BBC https://www.bbc.co.uk/programmes/b006q2x0
namespace BlazorSample;
public class Dalek
{
public int Units { get; set; }
}
在应用的 Program
文件中使用 AddCascadingValue 进行了以下注册:
- 具有
Dalek
属性值的Units
注册为固定级联值。 - 具有不同
Dalek
属性值的第二个Units
注册命名为“AlphaGroup
”。
builder.Services.AddCascadingValue(sp => new Dalek { Units = 123 });
builder.Services.AddCascadingValue("AlphaGroup", sp => new Dalek { Units = 456 });
以下 Daleks
组件显示级联值。
Daleks.razor
:
@page "/daleks"
<PageTitle>Daleks</PageTitle>
<h1>Root-level Cascading Value Example</h1>
<ul>
<li>Dalek Units: @Dalek?.Units</li>
<li>Alpha Group Dalek Units: @AlphaGroupDalek?.Units</li>
</ul>
<p>
Dalek© <a href="https://www.imdb.com/name/nm0622334/">Terry Nation</a><br>
Doctor Who© <a href="https://www.bbc.co.uk/programmes/b006q2x0">BBC</a>
</p>
@code {
[CascadingParameter]
public Dalek? Dalek { get; set; }
[CascadingParameter(Name = "AlphaGroup")]
public Dalek? AlphaGroupDalek { get; set; }
}
@page "/daleks"
<PageTitle>Daleks</PageTitle>
<h1>Root-level Cascading Value Example</h1>
<ul>
<li>Dalek Units: @Dalek?.Units</li>
<li>Alpha Group Dalek Units: @AlphaGroupDalek?.Units</li>
</ul>
<p>
Dalek© <a href="https://www.imdb.com/name/nm0622334/">Terry Nation</a><br>
Doctor Who© <a href="https://www.bbc.co.uk/programmes/b006q2x0">BBC</a>
</p>
@code {
[CascadingParameter]
public Dalek? Dalek { get; set; }
[CascadingParameter(Name = "AlphaGroup")]
public Dalek? AlphaGroupDalek { get; set; }
}
在以下示例中,Dalek
使用 CascadingValueSource<T>
注册为级联值,其中 <T>
为类型。
isFixed
标志指示值是否固定。 如果 false
成立的话,则所有收件人都已经订阅了更新通知。 订阅会产生开销并降低性能,因此,如果值不变,请将 isFixed
设置为 true
。
builder.Services.AddCascadingValue(sp =>
{
var dalek = new Dalek { Units = 789 };
var source = new CascadingValueSource<Dalek>(dalek, isFixed: false);
return source;
});
警告
将组件类型注册为根级级联值不会为该类型注册其他服务或允许在组件中激活服务。
将所需服务与级联值分开处理,并将它们与级联类型分开注册。
避免使用 AddCascadingValue 将组件类型注册为级联值。 相反,使用组建将 <Router>...</Router>
包装在 Routes
组件 (Components/Routes.razor
) 中,并采用全局交互式服务器端呈现(交互式 SSR)。 有关示例,请参阅 CascadingValue
组件 部分。
包含通知的根级级联值
调用 NotifyChangedAsync 发出更新通知可用于向多个 Razor 组件订阅者发出级联值已更改的信号。 对于采用静态服务器端呈现(静态 SSR)的订阅者,无法发出通知,因此订阅者必须采用交互式呈现模式。
在下面的示例中:
-
NotifyingDalek
INotifyPropertyChanged实现以通知客户端属性值已更改。Units
属性设置时,将调用PropertyChangedEventHandler(PropertyChanged
)。 - 订阅者可以触发
SetUnitsToOneThousandAsync
方法,将Units
设置为1,000,并引入模拟处理延迟。
请记住,对于生产级代码,状态的任何更改(类的任何属性值更改)都会导致所有订阅的组件重新渲染,不论它们使用状态的哪个部分。 我们建议创建精细类,将它们与特定订阅分开级联,以确保只有订阅了应用程序状态特定部分的组件才会受到更改的影响。
注意
Blazor Web App对于由服务器和客户端 (.Client
) 项目组成的解决方案,以下NotifyingDalek.cs
文件放置在.Client
项目中。
NotifyingDalek.cs
:
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class NotifyingDalek : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private int units;
public int Units
{
get => units;
set
{
if (units != value)
{
units = value;
OnPropertyChanged();
}
}
}
protected virtual void OnPropertyChanged(
[CallerMemberName] string? propertyName = default)
=> PropertyChanged?.Invoke(this, new(propertyName));
public async Task SetUnitsToOneThousandAsync()
{
// Simulate a three second delay in processing
await Task.Delay(3000);
Units = 1000;
}
}
下面的 CascadingStateServiceCollectionExtensions
从实现 CascadingValueSource<TValue> 的类型生成一个 INotifyPropertyChanged。
注意
Blazor Web App对于由服务器和客户端 (.Client
) 项目组成的解决方案,以下CascadingStateServiceCollectionExtensions.cs
文件放置在.Client
项目中。
CascadingStateServiceCollectionExtensions.cs
:
using System.ComponentModel;
using Microsoft.AspNetCore.Components;
namespace Microsoft.Extensions.DependencyInjection;
public static class CascadingStateServiceCollectionExtensions
{
public static IServiceCollection AddNotifyingCascadingValue<T>(
this IServiceCollection services, T state, bool isFixed = false)
where T : INotifyPropertyChanged
{
return services.AddCascadingValue<T>(sp =>
{
return new CascadingStateValueSource<T>(state, isFixed);
});
}
private sealed class CascadingStateValueSource<T>
: CascadingValueSource<T>, IDisposable where T : INotifyPropertyChanged
{
private readonly T state;
private readonly CascadingValueSource<T> source;
public CascadingStateValueSource(T state, bool isFixed = false)
: base(state, isFixed = false)
{
this.state = state;
source = new CascadingValueSource<T>(state, isFixed);
this.state.PropertyChanged += HandlePropertyChanged;
}
private void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e)
{
_ = NotifyChangedAsync();
}
public void Dispose()
{
state.PropertyChanged -= HandlePropertyChanged;
}
}
}
该类型的 PropertyChangedEventHandler(HandlePropertyChanged
)调用 CascadingValueSource<TValue> 的 NotifyChangedAsync 方法,以通知订阅者级联值已更改。 调用Task时将放弃NotifyChangedAsync,因为该调用仅表示调度到同步上下文的持续时间。 异常是在接收更新时,组件引发的上下文中,通过调度到渲染器来内部处理。 这与使用CascadingValue<TValue>处理异常的方式相同,而该方式不会通知关于通知接收方内部发生的异常。 事件处理程序在方法中 Dispose
断开连接,以防止内存泄漏。
Program
文件中,NotifyingDalek
被传递以创建CascadingValueSource<TValue>,初始Unit
值为 888 个单位。
builder.Services.AddNotifyingCascadingValue(new NotifyingDalek() { Units = 888 });
注意
Blazor Web App对于由服务器和客户端 (.Client
) 项目组成的解决方案,上述代码将放入每个项目的Program
文件中。
以下组件用于演示如何通过更改NotifyingDalek.Units
的值来通知订阅者。
Daleks.razor
:
<h2>Daleks component</h2>
<div>
<b>Dalek Units:</b> @Dalek?.Units
</div>
<div>
<label>
<span style="font-weight:bold">New Unit Count:</span>
<input @bind="dalekCount" />
</label>
<button @onclick="Update">Update</button>
</div>
<div>
<button @onclick="SetOneThousandUnits">Set Units to 1,000</button>
</div>
<p>
Dalek© <a href="https://www.imdb.com/name/nm0622334/">Terry Nation</a><br>
Doctor Who© <a href="https://www.bbc.co.uk/programmes/b006q2x0">BBC</a>
</p>
@code {
private int dalekCount;
[CascadingParameter]
public NotifyingDalek? Dalek { get; set; }
private void Update()
{
if (Dalek is not null)
{
Dalek.Units = dalekCount;
dalekCount = 0;
}
}
private async Task SetOneThousandUnits()
{
if (Dalek is not null)
{
await Dalek.SetUnitsToOneThousandAsync();
}
}
}
为了演示多个订阅者通知,以下 DaleksMain
组件呈现三 Daleks
个组件。 当更新一个Units
组件的单位计数(Dalek
)时,其他两个Dalek
组件的订阅者也将被更新。
DaleksMain.razor
:
@page "/daleks-main"
<PageTitle>Daleks Main</PageTitle>
<h1>Daleks Main</h1>
<Daleks />
<Daleks />
<Daleks />
在 DaleksMain
中为 NavMenu.razor
组件添加导航链接:
<div class="nav-item px-3">
<NavLink class="nav-link" href="daleks-main">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Daleks
</NavLink>
</div>
CascadingValueSource<TValue>由于本示例 (NotifyingDalek
) 中的类型是类类型,因此几乎可以满足任何状态管理功能规范要求。 但是,订阅会创建开销并降低性能,因此,在应用内对此方法的性能进行基准测试,并将其与其他 状态管理方法 进行比较,然后再在具有受限处理和内存资源的生产应用中采用此方法。
状态的任何改变(类的任何属性值更改)都会导致所有订阅组件重新渲染,而不论它们使用的是状态的哪个部分。 避免创建表示整个全局应用程序状态的单个大型类。 相反,请创建细粒度类,并将它们单独级联,通过特定的级联参数订阅,确保只有那些订阅了应用程序状态特定部分的组件会受到变化的影响。
CascadingValue
组件
祖先组件使用 Blazor 框架的 CascadingValue
组件提供级联值,该组件包装组件层次结构的子树,并向其子树中的所有组件提供单个值。
下面的示例演示了主题信息沿组件层次结构向下流动,以便为子组件中的按钮提供 CSS 样式的类。
以下 ThemeInfo
C# 类会指定主题信息。
注意
对于本部分中的示例,应用的命名空间为 BlazorSample
。 在自己的示例应用中试验代码时,请将应用的命名空间更改为示例应用的命名空间。
ThemeInfo.cs
:
namespace BlazorSample;
public class ThemeInfo
{
public string? ButtonClass { get; set; }
}
namespace BlazorSample;
public class ThemeInfo
{
public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses;
public class ThemeInfo
{
public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses;
public class ThemeInfo
{
public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses
{
public class ThemeInfo
{
public string ButtonClass { get; set; }
}
}
namespace BlazorSample.UIThemeClasses
{
public class ThemeInfo
{
public string ButtonClass { get; set; }
}
}
下面的布局组件将主题信息 (ThemeInfo
) 指定为构成 Body 属性布局主体的所有组件的级联值。
ButtonClass
分配有值 btn-success
,这是一种启动按钮样式。 组件层次结构中的任何后代组件都可以通过 ButtonClass
级联值来使用 ThemeInfo
属性。
MainLayout.razor
:
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<CascadingValue Value="theme">
<article class="content px-4">
@Body
</article>
</CascadingValue>
</main>
</div>
<div id="blazor-error-ui" data-nosnippet>
An unhandled error has occurred.
<a href="." class="reload">Reload</a>
<span class="dismiss">🗙</span>
</div>
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<CascadingValue Value="theme">
<article class="content px-4">
@Body
</article>
</CascadingValue>
</main>
</div>
<div id="blazor-error-ui" data-nosnippet>
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<CascadingValue Value="@theme">
<article class="content px-4">
@Body
</article>
</CascadingValue>
</main>
</div>
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<CascadingValue Value="@theme">
<div class="content px-4">
@Body
</div>
</CascadingValue>
</main>
</div>
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<CascadingValue Value="@theme">
<div class="content px-4">
@Body
</div>
</CascadingValue>
</div>
</div>
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<CascadingValue Value="theme">
<div class="content px-4">
@Body
</div>
</CascadingValue>
</div>
@code {
private ThemeInfo theme = new ThemeInfo { ButtonClass = "btn-success" };
}
Blazor Web App 为级联值提供了替代方法,与通过单个布局文件提供这些值相比,这种方法可以更广泛地应用于应用:
将
Routes
组件的标记包装在CascadingValue
组件中,以指定数据作为应用所有组件的级联值。以下示例从
ThemeInfo
组件级联Routes
数据。Routes.razor
:<CascadingValue Value="theme"> <Router ...> ... </Router> </CascadingValue> @code { private ThemeInfo theme = new() { ButtonClass = "btn-success" }; }
注意
不支持使用
Routes
组件将App
组件实例包装在Components/App.razor
组件(CascadingValue
)中。通过在服务集合生成器上调用 扩展方法,将AddCascadingValue指定为服务。
以下示例级联
ThemeInfo
文件中的数据Program
。Program.cs
builder.Services.AddCascadingValue(sp => new ThemeInfo() { ButtonClass = "btn-primary" });
有关详细信息,请参阅本文以下各节:
[CascadingParameter]
特性
为了使用级联值,后代组件使用 [CascadingParameter]
特性来声明级联参数。 级联值按类型绑定到级联参数。 本文后面的级联多个值部分中介绍了相同类型的级联多个值。
以下组件将 ThemeInfo
级联值绑定到级联参数,并且可以选择使用相同的名称 ThemeInfo
。 该参数用于设置 Increment Counter (Themed)
按钮的 CSS 类。
ThemedCounter.razor
:
@page "/themed-counter"
<PageTitle>Themed Counter</PageTitle>
<h1>Themed Counter Example</h1>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">
Increment Counter (Unthemed)
</button>
</p>
<p>
<button
class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)"
@onclick="IncrementCount">
Increment Counter (Themed)
</button>
</p>
@code {
private int currentCount = 0;
[CascadingParameter]
protected ThemeInfo? ThemeInfo { get; set; }
private void IncrementCount() => currentCount++;
}
@page "/themed-counter"
<PageTitle>Themed Counter</PageTitle>
<h1>Themed Counter Example</h1>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">
Increment Counter (Unthemed)
</button>
</p>
<p>
<button
class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)"
@onclick="IncrementCount">
Increment Counter (Themed)
</button>
</p>
@code {
private int currentCount = 0;
[CascadingParameter]
protected ThemeInfo? ThemeInfo { get; set; }
private void IncrementCount() => currentCount++;
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses
<h1>Themed Counter</h1>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">
Increment Counter (Unthemed)
</button>
</p>
<p>
<button
class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)"
@onclick="IncrementCount">
Increment Counter (Themed)
</button>
</p>
@code {
private int currentCount = 0;
[CascadingParameter]
protected ThemeInfo? ThemeInfo { get; set; }
private void IncrementCount()
{
currentCount++;
}
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses
<h1>Themed Counter</h1>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">
Increment Counter (Unthemed)
</button>
</p>
<p>
<button
class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)"
@onclick="IncrementCount">
Increment Counter (Themed)
</button>
</p>
@code {
private int currentCount = 0;
[CascadingParameter]
protected ThemeInfo? ThemeInfo { get; set; }
private void IncrementCount()
{
currentCount++;
}
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses
<h1>Themed Counter</h1>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">
Increment Counter (Unthemed)
</button>
</p>
<p>
<button class="btn @ThemeInfo.ButtonClass" @onclick="IncrementCount">
Increment Counter (Themed)
</button>
</p>
@code {
private int currentCount = 0;
[CascadingParameter]
protected ThemeInfo ThemeInfo { get; set; }
private void IncrementCount()
{
currentCount++;
}
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses
<h1>Themed Counter</h1>
<p>Current count: @currentCount</p>
<p>
<button @onclick="IncrementCount">
Increment Counter (Unthemed)
</button>
</p>
<p>
<button class="btn @ThemeInfo.ButtonClass" @onclick="IncrementCount">
Increment Counter (Themed)
</button>
</p>
@code {
private int currentCount = 0;
[CascadingParameter]
protected ThemeInfo ThemeInfo { get; set; }
private void IncrementCount()
{
currentCount++;
}
}
与常规组件参数类似,当级联值改变时,接受级联参数的组件会重新呈现。 例如,配置不同的主题实例会导致 ThemedCounter
部分中的 CascadingValue
组件重新呈现。
MainLayout.razor
:
<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<CascadingValue Value="theme">
<article class="content px-4">
@Body
</article>
</CascadingValue>
<button @onclick="ChangeToDarkTheme">Dark mode</button>
</main>
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };
private void ChangeToDarkTheme()
{
theme = new() { ButtonClass = "btn-secondary" };
}
}
CascadingValue<TValue>.IsFixed 可用于指示级联参数在初始化后不会更改。
级联值/参数和呈现模式边界
级联参数不会跨呈现模式边界传递数据:
交互式会话在不同于那些使用静态服务器端呈现(静态 SSR)的页面的上下文中运行。 生成页面的服务器甚至不必是稍后托管交互式服务器会话的计算机,包括 WebAssembly 组件,其服务器与客户端的所在的计算机不同。 静态服务器端呈现(静态 SSR)的好处是获得无状态纯 HTML 呈现的完整性能。
跨越静态和交互式渲染之间边界的状态必须可序列化。 组件可以是引用一连串大量其他对象的任意对象,包括呈现器、DI 容器和每个 DI 服务实例。 必须显式地让状态从静态 SSR 开始序列化,使其可用于随后的交互式呈现组件。 采用两种方法:
- 通过 Blazor 框架,通过静态 SSR 传递到交互式渲染边界的参数如果可 JSON 序列化,则会自动序列化,否则会引发错误。
- 储存在
PersistentComponentState
上的状态如果可序列化为 JSON,则可以自动序列化并恢复,或者引发错误。
级联参数不可序列化为 JSON,因为级联参数的典型使用模式有点类似于 DI 服务。 级联参数通常有平台专用的变体,因此,如果框架阻止开发人员使用服务器交互特有的版本或 WebAssembly 特有的版本,这对开发人员将毫无用处。 此外,许多级联参数值通常不可序列化,因此,如果必须停止使用所有不可序列化的级联参数值,则更新现有应用是不切实际的。
建议:
如果需要将状态作为级联参数提供给所有交互式组件,我们建议使用 根级级联值 或 带有通知的根级级级联值。 工厂模式可用,应用可以在启动后发出更新值。 根级级级联值可用于所有组件,包括交互式组件,因为它们是作为 DI 服务处理的。
组件库作者可以为库使用者创建一个扩展方法,与下文的相似:
builder.Services.AddLibraryCascadingParameters();
指示开发人员调用扩展方法。 这是一种合理的替代方法,可取代指导开发人员在
<RootComponent>
组件中添加MainLayout
组件这一环节。
级联多个值
若要在同一子树内级联多个相同类型的值,请向每个 Name 组件及其相应的 CascadingValue
提供唯一的 [CascadingParameter]
字符串。
在下面的示例中,两个 CascadingValue
组件级联 CascadingType
的不同实例:
<CascadingValue Value="parentCascadeParameter1" Name="CascadeParam1">
<CascadingValue Value="ParentCascadeParameter2" Name="CascadeParam2">
...
</CascadingValue>
</CascadingValue>
@code {
private CascadingType? parentCascadeParameter1;
[Parameter]
public CascadingType? ParentCascadeParameter2 { get; set; }
}
在后代组件中,级联参数按 Name 从祖先组件中接收其级联值:
@code {
[CascadingParameter(Name = "CascadeParam1")]
protected CascadingType? ChildCascadeParameter1 { get; set; }
[CascadingParameter(Name = "CascadeParam2")]
protected CascadingType? ChildCascadeParameter2 { get; set; }
}
跨组件层次结构传递数据
级联参数还允许组件跨组件层次结构传递数据。 请考虑下面的 UI 选项卡集示例,其中选项卡集组件维护一系列单独的选项卡。
注意
对于本部分中的示例,应用的命名空间为 BlazorSample
。 在自己的示例应用中试验代码时,请将命名空间更改为示例应用的命名空间。
创建选项卡在名为 ITab
的文件夹中实现的 UIInterfaces
接口。
UIInterfaces/ITab.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample.UIInterfaces;
public interface ITab
{
RenderFragment ChildContent { get; }
}
注意
有关 RenderFragment 的详细信息,请参阅 ASP.NET Core Razor 组件。
以下 TabSet
组件维护一组选项卡。 选项卡集的 Tab
组件(在本部分后面创建)为列表 (<li>...</li>
) 提供列表项 (<ul>...</ul>
)。
子 Tab
组件不会作为参数显式传递给 TabSet
。 子 Tab
组件是 TabSet
的子内容的一部分。 但是,TabSet
仍需要对每个Tab
组件的引用,以便它可以呈现标题和活动选项卡。若要在不要求其他代码的情况下启用此协调,组件TabSet
可以自行提供级联值,然后由后代Tab
组件选取。
TabSet.razor
:
@using BlazorSample.UIInterfaces
<!-- Display the tab headers -->
<CascadingValue Value="this">
<ul class="nav nav-tabs">
@ChildContent
</ul>
</CascadingValue>
<!-- Display body for only the active tab -->
<div class="nav-tabs-body p-4">
@ActiveTab?.ChildContent
</div>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
public ITab? ActiveTab { get; private set; }
public void AddTab(ITab tab)
{
if (ActiveTab is null)
{
SetActiveTab(tab);
}
}
public void SetActiveTab(ITab tab)
{
if (ActiveTab != tab)
{
ActiveTab = tab;
StateHasChanged();
}
}
}
后代 Tab
组件将包含的 TabSet
作为级联参数捕获。
Tab
组件将自己添加到 TabSet
和坐标以设置活动选项卡。
Tab.razor
:
@using BlazorSample.UIInterfaces
@implements ITab
<li>
<a @onclick="ActivateTab" class="nav-link @TitleCssClass" role="button">
@Title
</a>
</li>
@code {
[CascadingParameter]
public TabSet? ContainerTabSet { get; set; }
[Parameter]
public string? Title { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
private string? TitleCssClass =>
ContainerTabSet?.ActiveTab == this ? "active" : null;
protected override void OnInitialized()
{
ContainerTabSet?.AddTab(this);
}
private void ActivateTab()
{
ContainerTabSet?.SetActiveTab(this);
}
}
以下 ExampleTabSet
组件使用 TabSet
组件,其中包含三个 Tab
组件。
ExampleTabSet.razor
:
@page "/example-tab-set"
<TabSet>
<Tab Title="First tab">
<h4>Greetings from the first tab!</h4>
<label>
<input type="checkbox" @bind="showThirdTab" />
Toggle third tab
</label>
</Tab>
<Tab Title="Second tab">
<h4>Hello from the second tab!</h4>
</Tab>
@if (showThirdTab)
{
<Tab Title="Third tab">
<h4>Welcome to the disappearing third tab!</h4>
<p>Toggle this tab from the first tab.</p>
</Tab>
}
</TabSet>
@code {
private bool showThirdTab;
}