Sdílet prostřednictvím


Blazor ASP.NET základních kaskádových hodnot a parametrů

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete v tomto článku ve verzi .NET 9.

Upozorňující

Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v zásadách podpory .NET a .NET Core. Aktuální verzi najdete v tomto článku ve verzi .NET 9.

Důležité

Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.

Aktuální verzi najdete v tomto článku ve verzi .NET 9.

Tento článek vysvětluje, jak tok dat z nadřazené Razor komponenty do sestupných komponent.

Kaskádové hodnoty a parametry poskytují pohodlný způsob, jak tok dat v hierarchii komponent z nadřazené komponenty do libovolného počtu sestupných komponent. Na rozdíl od parametrů komponent nevyžadují kaskádové hodnoty a parametry přiřazení atributů pro každou sestupnou komponentu, ve které jsou data spotřebována. Kaskádové hodnoty a parametry také umožňují komponentám vzájemně koordinovat hierarchii komponent.

Poznámka:

Příklady kódu v tomto článku přijímají referenční typy s možnou hodnotou null (NRT) a statickou analýzu stavu null-stav kompilátoru .NET, které jsou podporovány v ASP.NET Core v .NET 6 nebo novější. Při cílení na ASP.NET Core 5.0 nebo starší odeberte označení typu null (?) z CascadingType?příkladu článku , @ActiveTab?, RenderFragment?, ITab?, TabSet?a string? typy.

Kaskádové hodnoty na kořenové úrovni

Kaskádové hodnoty kořenové úrovně lze zaregistrovat pro celou hierarchii komponent. Podporují se pojmenované kaskádové hodnoty a odběry pro oznámení o aktualizacích.

V příkladech této části se používá následující třída.

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; }
}

V souboru aplikace Program jsou provedeny následující registrace:AddCascadingValue

  • Dalek s hodnotou vlastnosti pro Units je registrován jako pevná kaskádová hodnota.
  • Druhá Dalek registrace s jinou hodnotou vlastnosti má Units název "AlphaGroup".
builder.Services.AddCascadingValue(sp => new Dalek { Units = 123 });
builder.Services.AddCascadingValue("AlphaGroup", sp => new Dalek { Units = 456 });

Následující Daleks komponenta zobrazuje kaskádové hodnoty.

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; }
}

V následujícím příkladu Dalek je registrován jako kaskádová hodnota pomocí CascadingValueSource<T>, kde <T> je typ. Příznak isFixed označuje, jestli je hodnota pevná. Pokud je false, všichni příjemci jsou přihlášeni k odběru oznámení o aktualizacích, které jsou vystaveny voláním NotifyChangedAsync. Předplatná vytvářejí režii a snižují výkon, takže pokud isFixed se hodnota nezmění, nastavte true ji.

builder.Services.AddCascadingValue(sp =>
{
    var dalek = new Dalek { Units = 789 };
    var source = new CascadingValueSource<Dalek>(dalek, isFixed: false);

    return source;
});

Upozorňující

Registrace typu komponenty jako kaskádové hodnoty kořenové úrovně neregistruje další služby pro typ nebo povolení aktivace služby v komponentě.

Zacházejte s požadovanými službami odděleně od kaskádových hodnot a zaregistrujte je odděleně od kaskádového typu.

AddCascadingValue Vyhněte se registraci typu komponenty jako kaskádové hodnoty. Místo toho zabalte <Router>...</Router> komponentu Routes (Components/Routes.razor) komponentou a přijměte globální interaktivní vykreslování na straně serveru (interaktivní SSR). Příklad najdete v CascadingValue části komponent .

CascadingValue komponenta

Nadřazená komponenta poskytuje kaskádovou hodnotu pomocí Blazor komponenty architektury CascadingValue , která zabalí podstrom hierarchie komponent a poskytuje jednu hodnotu všem komponentám v jeho podstromu.

Následující příklad ukazuje tok informací o motivu v hierarchii komponent, aby poskytoval třídu stylů CSS tlačítkům v podřízených komponentách.

ThemeInfo Následující třída jazyka C# určuje informace o motivu.

Poznámka:

V příkladech v této části je BlazorSampleobor názvů aplikace . Při experimentování s kódem ve vlastní ukázkové aplikaci změňte obor názvů aplikace na obor názvů ukázkové aplikace.

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; }
    }
}

Následující komponenta rozložení určuje informace o motivu (ThemeInfo) jako kaskádovou hodnotu pro všechny komponenty, které tvoří tělo Body rozložení vlastnosti. ButtonClass je přiřazena hodnota btn-success, což je bootstrap styl tlačítka. Libovolná sestupná komponenta v hierarchii komponent může tuto vlastnost použít ButtonClass prostřednictvím ThemeInfo kaskádové hodnoty.

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 Apps poskytují alternativní přístupy k kaskádovým hodnotám, které se v aplikaci používají obecněji než jejich vybavení prostřednictvím jednoho souboru rozložení:

  • Zabalte kód Routes komponenty do CascadingValue komponenty a určete data jako kaskádovou hodnotu pro všechny komponenty aplikace.

    Následující příklad kaskáduje ThemeInfo data z Routes komponenty.

    Routes.razor:

    <CascadingValue Value="theme">
        <Router ...>
            ...
        </Router>
    </CascadingValue>
    
    @code {
        private ThemeInfo theme = new() { ButtonClass = "btn-success" };
    }
    

    Poznámka:

    Routes Zabalení instance komponenty do App komponenty (Components/App.razor) komponentou CascadingValue se nepodporuje.

  • Zadejte kaskádovou hodnotu kořenové úrovně jako službu voláním AddCascadingValue metody rozšíření v tvůrci kolekce služeb.

    Následující příklad kaskáduje ThemeInfo data ze Program souboru.

    Program.cs

    builder.Services.AddCascadingValue(sp => 
        new ThemeInfo() { ButtonClass = "btn-primary" });
    

Další informace najdete v následujících částech tohoto článku:

Atribut [CascadingParameter]

Chcete-li použít kaskádové hodnoty, sestupné komponenty deklarují kaskádové parametry pomocí atributu[CascadingParameter]. Kaskádové hodnoty jsou vázané na kaskádové parametry podle typu. Kaskádové více hodnot stejného typu je popsáno v části Kaskádové více hodnot dále v tomto článku.

Následující komponenta ThemeInfo vytvoří vazbu kaskádové hodnoty na kaskádový parametr, volitelně pomocí stejného názvu ThemeInfo. Parametr slouží k nastavení třídy CSS pro Increment Counter (Themed) tlačítko.

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++;
    }
}

Podobně jako u běžného parametru komponenty se komponenty, které přijímají kaskádový parametr, změní při změně kaskádové hodnoty znovu. Například konfigurace jiné instance motivu způsobí ThemedCounter , že se komponenta z oddílu CascadingValue komponenty znovu vymění.

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 lze použít k označení, že se kaskádový parametr po inicializaci nezmění.

Kaskádové hodnoty/parametry a hranice režimu vykreslování

Kaskádové parametry nepředávají data přes hranice režimu vykreslování:

  • Interaktivní relace se spouštějí v jiném kontextu než stránky, které používají vykreslování na straně statického serveru (static SSR). Není nutné, aby server, který vytváří stránku, byl dokonce stejný počítač, který hostuje některé pozdější relace Interaktivního serveru, včetně komponent WebAssembly, kde je server jiným počítačem s klientem. Výhodou statického vykreslování na straně serveru (static SSR) je získání plného výkonu čistě bezstavového vykreslování HTML.

  • Stav překračující hranice mezi statickým a interaktivním vykreslováním musí být serializovatelný. Komponenty jsou libovolné objekty, které odkazují na rozsáhlý řetězec jiných objektů, včetně rendereru, kontejneru DI a každé instance služby DI. Musíte explicitně způsobit serializaci stavu ze statického SSR, aby byl dostupný v následných interaktivně vykreslených komponentách. Přijímají se dva přístupy:

    • Blazor Prostřednictvím architektury se parametry předávané přes statickou SSR do interaktivní hranice vykreslování serializují automaticky, pokud jsou serializovatelné ve formátu JSON nebo dojde k chybě.
    • Stav uložený v PersistentComponentState serializaci a automaticky se obnoví, pokud je serializovatelný ve formátu JSON nebo dojde k chybě.

Kaskádové parametry nejsou serializovatelné ve formátu JSON, protože typické vzory použití kaskádových parametrů jsou trochu podobné službám DI. Často existují varianty kaskádových parametrů specifické pro platformu, takže by pro vývojáře bylo neužitečné, kdyby architektura zastavila vývojáře v tom, aby měli verze specifické pro server nebo webAssembly specifické verze. Mnoho kaskádových hodnot parametrů obecně není serializovatelné, takže by bylo nepraktické aktualizovat existující aplikace, pokud byste museli přestat používat všechny neserializovatelné kaskádové hodnoty parametrů.

Doporučení:

  • Pokud potřebujete zpřístupnit stav pro všechny interaktivní komponenty jako kaskádový parametr, doporučujeme použít kaskádové hodnoty na kořenové úrovni. Model továrny je k dispozici a aplikace může po spuštění aplikace generovat aktualizované hodnoty. Kaskádové hodnoty na kořenové úrovni jsou k dispozici pro všechny komponenty, včetně interaktivních komponent, protože se zpracovávají jako služby DI.

  • Pro autory knihoven komponent můžete vytvořit metodu rozšíření pro uživatele knihovny podobně jako v následujícím příkladu:

    builder.Services.AddLibraryCascadingParameters();
    

    Dejte vývojářům pokyn, aby volali metodu rozšíření. Jedná se o zvukovou alternativu, která jim dává pokyn, aby do své <RootComponent> komponenty přidali komponentuMainLayout.

Kaskádové více hodnot

Pokud chcete kaskádovat více hodnot stejného typu ve stejném podstromu, zadejte jedinečný Name řetězec pro každou CascadingValue komponentu a jejich odpovídající [CascadingParameter] atributy.

V následujícím příkladu dvě CascadingValue komponenty kaskádují různé instance CascadingType:

<CascadingValue Value="parentCascadeParameter1" Name="CascadeParam1">
    <CascadingValue Value="ParentCascadeParameter2" Name="CascadeParam2">
        ...
    </CascadingValue>
</CascadingValue>

@code {
    private CascadingType? parentCascadeParameter1;

    [Parameter]
    public CascadingType? ParentCascadeParameter2 { get; set; }
}

V potomkové komponentě přijímají kaskádové parametry jejich kaskádové hodnoty z nadřazené komponenty Name:

@code {
    [CascadingParameter(Name = "CascadeParam1")]
    protected CascadingType? ChildCascadeParameter1 { get; set; }

    [CascadingParameter(Name = "CascadeParam2")]
    protected CascadingType? ChildCascadeParameter2 { get; set; }
}

Předávání dat napříč hierarchií komponent

Kaskádové parametry také umožňují komponentám předávat data napříč hierarchií komponent. Představte si následující příklad sady karet uživatelského rozhraní, kde komponenta sady karet udržuje řadu jednotlivých karet.

Poznámka:

V příkladech v této části je BlazorSampleobor názvů aplikace . Při experimentování s kódem ve vlastní ukázkové aplikaci změňte obor názvů na obor názvů ukázkové aplikace.

Vytvořte ITab rozhraní, které karty implementují ve složce s názvem UIInterfaces.

UIInterfaces/ITab.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.UIInterfaces;

public interface ITab
{
    RenderFragment ChildContent { get; }
}

Poznámka:

Další informace o RenderFragmentkomponentách RazorASP.NET Core.

Následující TabSet komponenta udržuje sadu karet. Komponenty sady Tab karet, které jsou vytvořeny později v této části, zadejte položky seznamu (<li>...</li>) pro seznam (<ul>...</ul>).

Podřízené Tab komponenty nejsou explicitně předány jako parametry .TabSet Místo toho jsou podřízené Tab komponenty součástí podřízeného TabSetobsahu souboru . Stále však potřebuje odkaz na každou TabSet komponentu, Tab aby mohl vykreslit záhlaví a aktivní kartu. Pokud chcete tuto koordinaci povolit bez nutnosti dalšího kódu, TabSet může se komponenta poskytnout jako kaskádová hodnota, která se pak vybere sestupně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();
        }
    }
}

Descendent Tab components capture the containing TabSet as a cascading parameter. Komponenty Tab se přidají do TabSet a souřadnice pro nastavení aktivní karty.

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);
    }
}

Následující ExampleTabSet komponenta používá komponentu TabSet , která obsahuje tři Tab komponenty.

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;
}

Další materiály