参数
一、组件参数
在使用组件时,可以通过组件参数将数据传递给组件,接收数据的组件,则需要使用[Parameter]
特性定义对应的公共属性 。 在下面的示例中,内置引用类型 (System.String
) 和用户定义的引用类型 (PanelBody
) 作为组件参数进行传递。
PanelBody.cs
namespace BlazorSample;
public class PanelBody
{
public string? Text { get; set; }
public string? Style { get; set; }
}
ParameterChild.razor
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">@Title</div>
<div class="card-body" style="font-style:@Body.Style">
@Body.Text
</div>
</div>
@code {
[Parameter]
public string Title { get; set; } = "Set By Child";
[Parameter]
public PanelBody Body { get; set; } =
new()
{
Text = "Set by child.",
Style = "normal"
};
}
Parameter.razor
@page "/parameter"
<PageTitle>Parameter</PageTitle>
<h1>Parameter Example</h1>
<h1>Child component (without attribute values)</h1>
<ParameterChild/>
<h1>Child component (with attribute values)</h1>
<ParameterChild Title="Set by Parent"
Body="@(new PanelBody() { Text = "Set by parent.", Style = "italic" })" />
在使用组件参数时,建议遵守如下规范
- 始终使用引号:根据 HTML5 规范,参数属性值的引号是可选的。 例如,
Value=this
和Value="this"
都是支持的,但是建议始终使用引号。 - 对于Razor表达式之外的文本,始终避免使用
@
。例如IsFixed="true"
,MyComponent="this"
和MyComponent="null"
等。 - 在定义组件参数时,应该声明为自动属性。不要在
get
或set
访问器中放置自定义逻辑,因为组件参数专门用作父组件向子组件传送信息的通道。 如果子组件属性的set
访问器包含导致父组件重新渲染的逻辑,则会导致一个无限的渲染循环。如果要转换已接收的参数值,建议创建另一个属性或方法,用于基于参数属性提供转换后的数据。
不支持异步表达式
在渲染组件时,Blazor 不能在 Razor 表达式中执行异步工作,例如<ParameterChild Title="@await ..." />
。 这是因为 Blazor 是为渲染交互式 UI 而设计的。 在交互式 UI 中,屏幕必须始终显示某些内容,因此阻止渲染流是没有意义的。 相反,异步工作是在一个异步生命周期事件期间执行的。 在每个异步生命周期事件之后,组件可能会再次渲染。
若要在异步获取参数的值,组件可以使用 OnInitializedAsync
生命周期事件来实现。
示例
<ParameterChild Title="@title" />
@code {
private string? title;
protected override async Task OnInitializedAsync()
{
title = await ...;
}
}
不支持混合赋值
Blazor中,不支持使用文本和表达式结果拼接并赋值给参数,例如<ParameterChild Title="Set by @(panelData.Title)"/>
。
若要使用混合赋值,可以使用方法、额外的字段或属性的方式来事项。
[EditorRequired]
特性
在定义组件参数属性时,可以使用[EditorRequired]
特性来约定该属性必须提供参数值,否则编译器或生成工具可能会向用户显示警告。(只是警告,浏览器中还是可以访问的)此特性仅在用[Parameter]
特性标记的属性上有效。
RequiredTest.razor
<h3>@Title</h3>
@code {
//[Parameter, EditorRequired]
[Parameter]
[EditorRequired]
public string? Title { get; set; } = "TitleTest";
}
RequiredComponent.razor
@page "/required"
@rendermode InteractiveServer
<h1>Required Test</h1>
<RequiredTest Title="提供参数值就不会有报警提示"/>
支持元组
Blazor中,组件参数和RenderFragment
类型是支持元组的。
RenderTupleChild.razor
<div class="card w-50" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Tuple Card</div>
<div class="card-body">
<ul>
<li>Integer: @Data?.Item1</li>
<li>String: @Data?.Item2</li>
<li>Boolean: @Data?.Item3</li>
</ul>
</div>
</div>
@code {
[Parameter]
public (int, string, bool)? Data { get; set; }
}
RenderTupleParent.razor
@page "/render-tuple-parent"
<PageTitle>Render Tuple Parent</PageTitle>
<h1>Render Tuple Parent Example</h1>
<RenderTupleChild Data="data" />
@code {
private (int, string, bool) data = new(999, "I aim to misbehave.", true);
}
也可以用命名元组的方式
NamedTupleChild.razor
<div class="card w-50" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Tuple Card</div>
<div class="card-body">
<ul>
<li>Integer: @Data?.TheInteger</li>
<li>String: @Data?.TheString</li>
<li>Boolean: @Data?.TheBoolean</li>
</ul>
</div>
</div>
@code {
[Parameter]
public (int TheInteger, string TheString, bool TheBoolean)? Data { get; set; }
}
NamedTuples.razor
@page "/named-tuples"
<PageTitle>Named Tuples</PageTitle>
<h1>Named Tuples Example</h1>
<NamedTupleChild Data="data" />
@code {
private (int TheInteger, string TheString, bool TheBoolean) data =
new(999, "I aim to misbehave.", true);
}
支持null
值
默认情况下,Blazor组件参数不允许接受null
值,传入null
值会导致编译时或运行时错误。如果要允许传入null
值,则需要在定义组件参数时,使用AllowNull
特性。
示例
//[Parameter]
//[AllowNull]
[Parameter,AllowNull]
public string NullableParameter { get; set; }
二、路由参数
使用[Parameter]
特性定义公共属性,还可以用来接收路由中的参数。
@page "/route-parameter/{text}"
<p>Blazor is @Text!</p>
@code {
[Parameter]
public string? Text { get; set; }
}
三、子内容渲染片段
1、基本使用
在组件中,可以使用RenderFragment
类型定义组件参数ChildContent
,用于接收该组件的子内容。通过ChildContent
参数,可以在组件内部定义一个区域,用于渲染传递进来的子内容,从而让组件更加灵活,可以根据需要动态地渲染不同的内容。
- 如果作为子内容渲染片段,那么
RenderFragment
类型的组件参数的属性名必须为ChildContent
,也可以反过来想,如果在使用组件时,给组件添加了子内容,就相当于设置组件中的ChildContent
组件参数,因此组件中必须定义有名为ChildContent
且类型为RenderFragment
的组件参数。 RenderFragment
不支持事件回叫
RenderFragmentTest.razor
<h3>子内容片段测试</h3>
<div>
@ChildContent
</div>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
RenderFragmentPanel.razor
@page "/render-fragment"
<h3>RenderFragmentPanel</h3>
<RenderFragmentTest>
<h2>子内容</h2>
</RenderFragmentTest>
可复用的渲染片段
除了用于子内容渲染片段,还可以通过RenderFragment
来自定义渲染片段。
示例
@RenderWelcomeInfo
<p>Render the welcome info a second time:</p>
@RenderWelcomeInfo
@code {
private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!</p>;
}
渲染片段参数
可以通过RenderFragment<TValue>
向渲染片段传递参数。
示例
@page "/razor-template"
<PageTitle>Razor Template</PageTitle>
<h1>Razor Template Example</h1>
@timeTemplate
@petTemplate(new Pet { Name = "Nutty Rex" })
@code {
private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.</p>;
private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet: @pet.Name</p>;
private class Pet
{
public string? Name { get; set; }
}
}
2、模板化组件
模板化组件,其实就是在子组件中使用一个或多个RenderFragment
或 RenderFragment<TValue>
定义模板化组件(组件参数)。然后父组件在使用子组件时,通过给子组件设置对应的子内容(对应名称的组件)渲染片段,传递给子组件对应的模板化组件。至于子组件中要怎么使用模板化组件,根据需求来就行了。
- 父组件在使用子组件并设置子组件的子内容时,根据子组件中设置的模板化组件参数名来指定模板参数即可,例如下面例子中,
<TableHeader>...</TableHeader>
和<RowTemplate>...<RowTemplate>
为TableTemplate
组件的TableHeader
和RowTemplate
提供RenderFragment<TValue>
模板。
TableTemplate.razor
@typeparam TItem
@using System.Diagnostics.CodeAnalysis
<table class="table">
<thead>
<tr>@TableHeader</tr>
</thead>
<tbody>
@foreach (var item in Items)
{
if (RowTemplate is not null)
{
<tr>@RowTemplate(item)</tr>
}
}
</tbody>
</table>
@code {
[Parameter]
public RenderFragment? TableHeader { get; set; }
[Parameter]
public RenderFragment<TItem>? RowTemplate { get; set; }
[Parameter, AllowNull]
public IReadOnlyList<TItem> Items { get; set; }
}
指定子组件列表参数的子项名
如果要为子组件的列表项的子项定义名称,可以使用组件元素上的Context
属性
Pets1.razor
@page "/pets-1"
<PageTitle>Pets 1</PageTitle>
<h1>Pets Example 1</h1>
<TableTemplate Items="pets" Context="pet">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@pet.PetId</td>
<td>@pet.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string? Name { get; set; }
}
}
指定模板化组件的子项名
可以单独给模板组件设置子项名,也是使用Context
属性
Pets2.razor
@page "/pets-2"
<PageTitle>Pets 2</PageTitle>
<h1>Pets Example 2</h1>
<TableTemplate Items="pets">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate Context="pet">
<td>@pet.PetId</td>
<td>@pet.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string? Name { get; set; }
}
}
直接使用隐式参数
类型为 RenderFragment<TValue>
的组件参数具有一个名为 context
的隐式参数,可以直接使用该参数作为子项对象
- 注意,
RenderFragment<TValue>
才有context
隐式参数,RenderFragment
没有。
Pets3.razor
@page "/pets-3"
<PageTitle>Pets 3</PageTitle>
<h1>Pets Example 3</h1>
<TableTemplate Items="pets">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@context.PetId</td>
<td>@context.Name</td>
</RowTemplate>
</TableTemplate>
@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};
private class Pet
{
public int PetId { get; set; }
public string? Name { get; set; }
}
}
四、组件参数的泛型支持
1、常规使用
在Blazor组件中,如果希望在组件参数上使用泛型,需要在组件顶部使用Razor的@typeparam
指令来声明泛型类型参数,然后再在定义组件参数时使用。
TypeParamTest.razor
@typeparam T
<h3>泛型参数测试</h3>
@foreach (var t in TList!)
{
<div>@t</div>
}
@code {
[Parameter]
public List<T>? TList { get; set; }
}
TypeParamPanel.razor
@page "/type-param"
<TypeParamTest T="string" TList="@(new List<string>{"a", "b", "c"})"/>
<TypeParamTest T="int" TList="@(new List<int>{1, 2, 3})"/>
自动类型推断
实际上,如果在使用组件时,给泛型组件参数设置的值具有明确的类型时,可以不用显式设置泛型类型,C#会自动进行泛型类型推断的,也就是可以简化如下
TypeParamPanel.razor
@page "/type-param"
<TypeParamTest TList="@(new List<string>{"a", "b", "c"})"/>
<TypeParamTest TList="@(new List<int>{1, 2, 3})"/>
2、级联泛型类型传递
级联传递泛型类型,简单的说就是在父组件中去设置子组件中的泛型组件参数中的泛型类型,要实现这一点就需要在父组件的顶部联合使用@attribute [CascadingTypeParameter(nameof(T))]
和@typeparam T
。
TypeParamTest.razor
@typeparam T
<h3>泛型参数测试</h3>
@foreach (var t in TList!)
{
<div>@t</div>
}
@code {
[Parameter]
public List<T>? TList { get; set; }
}
CascadingTypeParameterTest.razor
@attribute [CascadingTypeParameter(nameof(T))]
@typeparam T
@ChildContent
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
CascadingTypeParameterPanel.razor
@page "/cascading-type-parameter"
<h3>级联泛型参数类型传递测试</h3>
<CascadingTypeParameterTest T="string">
<TypeParamTest TList="@StrList"/>
</CascadingTypeParameterTest>
<CascadingTypeParameterTest T="int">
<TypeParamTest TList="@IntList" />
</CascadingTypeParameterTest>
@code {
private List<string> StrList = new List<string> { "A","B","C" };
private List<int> IntList = new List<int> { 1, 2, 3 };
}
内置组件传递泛型级联值
其实就是传递级联值,但是这个级联值设置了泛型参数,关于级联值的具体使用,下文中有详细笔录。
TypeParamTest.razor
@typeparam T
<h3>泛型参数测试</h3>
@foreach (var t in TList!)
{
<div>@t</div>
}
@code {
[CascadingParameter]
public List<T>? TList { get; set; }
}
CascadingTypeParameterTest.razor
@attribute [CascadingTypeParameter(nameof(T))]
@typeparam T
@ChildContent
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
CascadingTypeParameterPanel.razor
@page "/cascading-type-parameter"
<h3>级联泛型参数类型传递测试</h3>
<CascadingValue Value="@StrList">
<CascadingTypeParameterTest T="string">
<TypeParamTest/>
</CascadingTypeParameterTest>
</CascadingValue>
<CascadingValue Value="@IntList">
<TypeParamTest T="int"/>
</CascadingValue>
@code {
private List<string> StrList = new List<string> { "A","B","C" };
private List<int> IntList = new List<int> { 1, 2, 3 };
}
五、避免覆盖参数
观察下面的示例中,在父组件中调用StateHasChanged()
时:
第一个ShowMoreExpander组件设置了子内容,因此在父组件中调用StateHasChanged()
后会自动重新渲染该组件,并且会将InitiallyExpanded的值覆盖为其初始值false
。
第二个ShowMoreExpander组件只设置了组件参数,且父组件中设置的组件参数值没有发生变化,因此在父组件中调用StateHasChanged()
后不会重新渲染该组件。
ShowMoreExpander.razor
<div @onclick="ShowMore" class="card bg-light mb-3" style="width:30rem">
<div class="card-header">
<h2 class="card-title">Show more (<code>Expanded</code> = @InitiallyExpanded)</h2>
</div>
@if (InitiallyExpanded)
{
<div class="card-body">
<p class="card-text">@ChildContent</p>
</div>
}
</div>
@code {
[Parameter]
public bool InitiallyExpanded { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
private void ShowMore()
{
InitiallyExpanded = true;
}
}
Expanders.razor
@page "/expanders"
<PageTitle>Expanders</PageTitle>
<h1>Expanders Example</h1>
<ShowMoreExpander InitiallyExpanded=false>
Expander 1 content
</ShowMoreExpander>
<ShowMoreExpander InitiallyExpanded=false/>
<button @onclick="StateHasChanged">Call StateHasChanged</button>
如上述例子,在父组件中调用StateHasChanged()
时,如果发生了子组件的重新渲染,则会覆盖掉子组件中内部发生了变化的组件参数。要避免这一点,可以在组件中使用私有字段来保留它的状态,将子组件修改如下:
ShowMoreExpander.razor
<div @onclick="Expand" class="card bg-light mb-3" style="width:30rem">
<div class="card-header">
<h2 class="card-title">Show more (<code>Expanded</code> = @expanded)</h2>
</div>
@if (expanded)
{
<div class="card-body">
<p class="card-text">@ChildContent</p>
</div>
}
</div>
@code {
private bool expanded;
[Parameter]
public bool InitiallyExpanded { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
{
expanded = InitiallyExpanded;
}
private void Expand()
{
expanded = true;
}
}
六、属性展开和任意参数
1、属性展开
当我们在Razor组件中设置元素的属性时,经常会遇到要设置很多属性的情况,Razor组件支持将这些属性全部放到一个
Dictionary<string, object>
对象中,并在元素上通过Razor的属性指令@attributes
进行展开。
查看下面示例,两个Input
元素在属性的设置上效果是一样的。
示例
@page "/splat"
<PageTitle>SPLAT!</PageTitle>
<h1>Splat Parameters Example</h1>
<input maxlength="@maxlength"
placeholder="@placeholder"
required="@required"
size="@size" />
<input @attributes="InputAttributes" />
@code {
private string maxlength = "10";
private string placeholder = "Input placeholder text";
private string required = "required";
private string size = "50";
private Dictionary<string, object> InputAttributes { get; set; } = new()
{
{ "maxlength", "10" },
{ "placeholder", "Input placeholder text" },
{ "required", "required" },
{ "size", "50" }
};
}
在元素中使用@attributes
时,要注意其摆放位置,在展开属性时,是从右到左的,也就是右边优先于左边,因此如果有同名属性放在@attributes
的右侧,那么会优先使用右侧的属性,可以参考下方示例:
示例
@page "/splat"
<PageTitle>SPLAT!</PageTitle>
<h1>Splat Parameters Example</h1>
<input @attributes="InputAttributes" placeholder="righter font"/>
@code {
private Dictionary<string, object> InputAttributes { get; set; } = new()
{
{ "maxlength", "10" },
{ "placeholder", "Input placeholder text" },
{ "required", "required" },
{ "size", "50" }
};
}
2、任意参数
如果传入的组件参数过多,可以在定义组件参数时,在[Parameter]
特性上设置CaptureUnmatchedValues=true
,允许该组件参数接纳所有传入的且不匹配其他任何组件参数的参数值。
- 组件中只能使用
CaptureUnmatchedValues
定义一个组件参数 - 与
CaptureUnmatchedValues
一起使用的属性类型必须可以使用键值存储的集合,例如Dictionary<string, object>
、IEnumerable<KeyValuePair<string, object>>
或IReadOnlyDictionary<string, object>
。
AttributeOrderChild.razor
<input @attributes="AdditionalAttributes"/>
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}
AttributeOrder.razor
@page "/attribute-order"
<PageTitle>Attribute Order</PageTitle>
<h1>Attribute Order Example</h1>
<AttributeOrderChild maxlength="10" placeholder="Input placeholder text" required="required" size="50"/>
级联值
在Blazor中,可以使用级联值将数据从上级组件中传递给下级组件。
一、根级联值的注册
可以为整个组件层次结构注册根级别级联值,支持用于更新通知的命名级联值和订阅。
固定的根级联值
要注册根级别级联值,可以在Program.cs
中使用IServiceCollection
对象的扩展方法AddCascadingValue
进行注册。
IServiceCollection AddCascadingValue<TValue>(Func<IServiceProvider, TValue> initialValueFactory)
:注册指定实例对象为固定的级联值。IServiceCollection AddCascadingValue<TValue>(string name, Func<IServiceProvider, TValue> initialValueFactory)
:注册指定实例对象为具名的级联值。
builder.Services.AddCascadingValue(sp => new Dalek { Units = 123 });
builder.Services.AddCascadingValue("AlphaGroup", sp => new Dalek { Units = 456 });
注意,将组件类型注册为根级级联值不会为该类型注册其他服务或允许在组件中激活服务,因此应该避免使用AddCascadingValue
将组件类型注册为级联值,可以使用CascadingValue
组件。
动态的根级联值
默认情况下,级联值是支持更新通知的,级联值发生变化时会自动调用NotifyChangedAsync
发出。
- 注意,与常规组件参数类似,当级联值改变时,接受级联值的组件会重新渲染,因此订阅会产生开销并降低性能,所以如果值不变,请将
isFixed
设置为true
,默认为false
。
builder.Services.AddCascadingValue(sp =>
{
var dalek = new Dalek { Units = 789 };
var source = new CascadingValueSource<Dalek>(dalek, true);
return source;
});
二、[CascadingParameter]特性
在下级组件中如果想要使用级联值,需要使用[CascadingParameter]
特性来进行属性声明,声明后,该属性则接收指定的级联值作为属性值。
- 注意,级联参数不会跨渲染模式传递数据,例如在Blazor Web App Auto项目中,如果只在服务器端做级联值的定义,那么到了WebAssembly阶段,就找不到这个级联值了,反之亦然。
@code {
//根据属性类型寻找级联值
[CascadingParameter]
public Dalek? Dalek { get; set; }
//根据指定名称寻找级联值
[CascadingParameter(Name = "AlphaGroup")]
public Dalek? AlphaGroupDalek { get; set; }
}
三、CascadingValue组件
CascadingValue
:级联值传递组件,可以通过设置Value属性,往下级组件传递单个级联值。
Value
:CascadingValue
组件的属性,用于设置级联值。Name
:CascadingValue
组件的属性,用于设置级联值的名称,可以不设。默认为接收变量的变量名。IsFixed
:CascadingValue
组件的属性,默认为false
,当为true
时表示级联值是固定不变的,不需要进行更新通知。
这里以传递自定义的主题信息类型ThemeInfo
实例为例子。
ThemeInfo.cs
public class ThemeInfo
{
public string? ButtonClass { get; set; }
}
布局级联值
可以在布局组件MainLayout.razor
中将主题内容包含在CascadingValue
组件中,通过Value
属性设定级联值进行传递。
MainLayout.razor
......
<CascadingValue Value="@theme">
<article class="content px-4">
@Body
</article>
</CascadingValue>
......
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
然后在要用到这个级联值的组将上通过CascadingParameter
特性声明。
CascadingValueTest.razor
@page "/cvt"
@rendermode InteractiveWebAssembly
<h1>@theme?.Units</h1>
@code {
[CascadingParameter]
public ThemeInfo? theme { get; set; }
}
全局级联值
Blazor Web提供了适用于整个应用的级联值设置方法,就是在Routes
组件中,将Router
组件包装在CascadingValue
组件中。
Routes.razor
<CascadingValue Value="@theme">
<Router ...>
...
</Router>
</CascadingValue>
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
注意,不支持在App.razor
组件中,将Routes.razor
组件包装在CascadingValue
组件中。
级联多个值
如果想要同时向子级组件传递多个级联值,可以通过CascadingValue
嵌套并指定级联值名称的方式来进行。
Routes.razor
<CascadingValue Value="@a" Name="Value1">
<CascadingValue Value="@b" Name="Value2">
<Router ......>
......
</Router>
</CascadingValue>
</CascadingValue>
@code {
private int a = 1;
private int b = 2;
}
跨组件传递级联值
跨组件传递级联值,这里的意思应该是子组件向父组件传递,其实就是CascadingValue
组件的一种妙用,其核心在于父组件使用
RenderFragment
获取子内容,然后在组件中通过CascadingValue
组件向子内容传递自己。子组件再通过[CascadingParameter]
获取到父组件对象后,将想要传递的东西传递过去。
- 注意,这里要将父组件的所有子组件作为渲染能容,因此
RenderFragment
用法是固定的,其必须用[Parameter]
特性,且变量名称必须为ChildContent
以下为官方提供的示例,TabSet组件为Tab的父组件,用于管理多个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.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.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;
}