次の方法で共有


ASP.NET Core Blazor の値とパラメーターのカスケード

注意

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

この記事では、先祖の Razor コンポーネントから子孫のコンポーネントにデータをフローさせる方法について説明します。

"カスケード値とパラメーター" の使用は、コンポーネント階層で先祖コンポーネントから下位の任意の数の子孫コンポーネントにデータをフローさせる便利な方法です。 カスケード値およびパラメーターでは、コンポーネント パラメーターとは異なり、データが使用される各子孫コンポーネントに属性を割り当てる必要がありません。 また、カスケード値とパラメーターを使用すると、コンポーネント階層全体でコンポーネントを相互連携させることができます。

注意

この記事のコード例では、null 許容参照型 (NRT) と .NET コンパイラの null 状態スタティック分析を採用しています。これは、.NET 6 以降の ASP.NET Core でサポートされています。 .NET 5 以前を対象とする場合は、記事の例の?CascadingType?@ActiveTab?RenderFragment?ITab?、およびTabSet?型から null 型の指定 (string?) を削除します。

ルート レベルのカスケード値

ルート レベルのカスケード値は、コンポーネント階層全体に登録できます。 更新通知の名前付きカスケード値とサブスクリプションがサポートされています。

このセクションの例では、次のクラスを使用します。

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 のプロパティ値を持つ 2 つ目の 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; }
}

次の例では、DalekCascadingValueSource<T> を使用してカスケード値として登録されます。ここで、<T> は型です。 isFixed フラグは、値が固定されているかどうかを示します。 false場合、すべての受信者が更新通知をサブスクライブします。 サブスクリプションではオーバーヘッドが発生し、パフォーマンスが低下するため、値が変更されない場合は isFixedtrue に設定します。

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>値が 888 単位のUnitが作成されます。

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 コンポーネントは 3 つの Daleks コンポーネントをレンダリングします。 1 つのUnits コンポーネントのユニット数 (Dalek) が更新されると、他の 2 つのDalek コンポーネント サブスクライバーが更新されます。

DaleksMain.razor:

@page "/daleks-main"

<PageTitle>Daleks Main</PageTitle>

<h1>Daleks Main</h1>

<Daleks />

<Daleks />

<Daleks />

DaleksMainNavMenu.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) はクラス型であるため、ほぼすべての状態管理機能の仕様要件を満たすことができます。 ただし、サブスクリプションではオーバーヘッドが発生し、パフォーマンスが低下するため、アプリでこのアプローチのパフォーマンスをベンチマークし、他の 状態管理アプローチ と比較してから、処理とメモリのリソースが制限された運用アプリで採用します。

状態の変更 (クラスのプロパティ値の変更) により、使用する状態のどの部分に関係なく、サブスクライブされているすべてのコンポーネントが再レンダリングされます。 グローバル アプリケーションの状態全体を表す 1 つの大規模なクラスを作成しないでください。 代わりに、きめ細かいクラスを作成し、それらをカスケード パラメーターに対する特定のサブスクリプションと個別にカスケードして、アプリケーション状態の特定の部分にサブスクライブされているコンポーネントのみが変更の影響を受けないようにします。

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 には、Bootstrap ボタン形式の 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 には、1 つのレイアウト ファイルを介して提供する場合より広くアプリに適用されるカスケード値に関して、別の方法が用意されています。

  • 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 から状態をシリアル化して、後続の対話形式でレンダリングされたコンポーネントで使用できるようにする必要があります。 次の 2 つの方法が採用されています:

    • Blazor フレームワークを介して、静的 SSR 経由で対話型レンダリング境界に渡されるパラメータは、JSON シリアル化可能な場合、またはエラーがスローされた場合、自動的にシリアル化されます。
    • PersistentComponentState に格納されている状態は、JSON シリアル化可能な場合、またはエラーがスローされた場合、自動的にシリアル化および復旧されます。

カスケード パラメーターの一般的な使用パターンは DI サービスに類似したものであるため、カスケード パラメーターは JSON シリアル化できません。 多くの場合、カスケード パラメーターにはプラットフォーム固有のバリエーションがあるため、フレームワークによって開発者がサーバー対話型固有のバージョンまたは WebAssembly 固有のバージョンを持つことを停止した場合、開発者には役に立ちません。 また、一般的に多くのカスケード パラメーター値はシリアル化できないため、すべての非シリアル化可能なカスケード パラメーター値の使用を停止する必要がある場合は、既存のアプリを更新することは現実的ではありません。

レコメンデーション:

  • カスケード パラメーターとしてすべての対話型コンポーネントで状態を使用できるようにする必要がある場合は、ルート レベルのカスケード値またはルート レベルのカスケード値を通知と共に使用することをお勧めします。 ファクトリ パターンを使用でき、アプリはアプリの起動後に更新された値を出力できます。 ルート レベルのカスケード値は、DI サービスとして処理されるため、対話型コンポーネントを含むすべてのコンポーネントで使用できます。

  • コンポーネント ライブラリ作成者の場合は、次のようなライブラリ コンシューマー用の拡張メソッドを作成できます:

    builder.Services.AddLibraryCascadingParameters();
    

    拡張機能メソッドを呼び出すように開発者に指示します。 これは、<RootComponent> コンポーネントに MainLayout コンポーネントを追加するように指示しなくて済むための安全な代替手段です。

複数の値のカスケード

同じサブツリー内で同じ型の値を複数カスケードするには、各 Name コンポーネントとそれに対応する CascadingValueに一意の [CascadingParameter] 文字列を指定します。

次の例では、2 つの 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 コンポーネントは、3 つの 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;
}

その他のリソース