ASP.NET Core Razor コンポーネント
この記事では、Razor 構文、コンポーネントの名前付け、名前空間、コンポーネント パラメーターに関するガイダンスなど、Blazor アプリで Razor コンポーネントを作成して使用する方法について説明します。
Blazor アプリは、"Razor コンポーネント" (非公式には "Blazor コンポーネント" と呼ばれます) を使って構築されます。 コンポーネントは、動的な動作を有効にするための処理ロジックを使用するユーザー インターフェイス (UI) の自己完結型の部分です。 コンポーネントは、入れ子にしたり、再利用したり、プロジェクト間で共有したり、MVC および Razor ページ アプリで使用したりすることができます。
コンポーネント クラス
コンポーネントは、.razor
ファイル拡張子の Razor コンポーネント ファイルで C# と HTML マークアップの組み合わせを使用して実装されます。
Razor の構文
コンポーネントでは Razor 構文を使用します。 コンポーネント、''ディレクティブ'' および ''ディレクティブ属性'' では、2 つの Razor 機能が広く使用されています。 これらは、Razor マークアップに表示される @
というプレフィックスが付いた予約キーワードです。
- ディレクティブ: コンポーネント マークアップの解析方法または関数を変更します。 たとえば、
@page
ディレクティブでは、ルート テンプレートでルーティング可能なコンポーネントを指定します。このディレクティブには、ブラウザーの特定の URL でのユーザーの要求により直接到達できます。 - ディレクティブ属性: コンポーネント要素の解析方法または関数を変更します。 たとえば、
<input>
要素の@bind
ディレクティブ属性では、データをその要素の値にバインドします。
コンポーネントで使用されるディレクティブとディレクティブ属性については、この記事および Blazor ドキュメント セットの他の記事で詳しく説明されています。 Razor の構文の一般的な情報については、「ASP.NET Coreの Razor 構文リファレンス」をご覧ください。
名前
コンポーネントの名前は大文字で始める必要があります。
ProductDetail.razor
は有効です。productDetail.razor
が無効です。
Blazor ドキュメント全体で使用される共通の Blazor の名前付け規則は次のとおりです。
- コンポーネント ファイルのパスでは、パスカル ケース†が使用され、コンポーネントのコード例が示される前に表示されます。 パスは一般的なフォルダーの場所を示します。 たとえば、
Pages/ProductDetail.razor
は、ProductDetail
コンポーネントのファイル名がProductDetail.razor
で、そのコンポーネントがアプリのPages
フォルダーに存在することを示します。 - ルーティング可能なコンポーネントのコンポーネント ファイル パスは、コンポーネントのルート テンプレート内の単語間のスペースに表示されるハイフン付きの URL に一致します。 たとえば、ルート テンプレートが
/product-detail
(@page "/product-detail"
) のProductDetail
コンポーネントは、ブラウザーの相対 URL/product-detail
で要求されます。
† パスカル ケース (大文字のキャメル ケース) は、スペースと句読点を使用せず、大文字の各単語の最初の文字 (最初の単語を含む) を使用する名前付け規則です。
ルーティング
Blazor でのルーティングは、@page
ディレクティブを使用するアプリ内のアクセス可能な各コンポーネントへのルート テンプレートを提供することで実現します。 @page
ディレクティブを含む Razor ファイルがコンパイルされると、生成されたクラスに、ルート テンプレートを指定する RouteAttribute が指定されます。 実行時に、ルーターによって RouteAttribute を持つコンポーネント クラスが検索され、要求された URL に一致するルート テンプレートを使用するコンポーネントがレンダリングされます。
次の HelloWorld
コンポーネントでは、/hello-world
のルート テンプレートを使用します。 コンポーネントのレンダリングされた Web ページには、相対 URL /hello-world
で到達できます。 既定のプロトコル、ホスト、およびポートを使用して Blazor アプリをローカルで実行する場合、HelloWorld
コンポーネントはブラウザーの https://localhost:5001/hello-world
で要求されます。 Web ページを生成するコンポーネントは通常、Pages
フォルダーに存在しますが、入れ子になったフォルダー内などのコンポーネントを保持するために任意のフォルダーを使用できます。
Pages/HelloWorld.razor
:
@page "/hello-world"
<h1>Hello World!</h1>
コンポーネントをアプリの UI ナビゲーションに追加するかどうかに関係なく、前のコンポーネントはブラウザーの /hello-world
で読み込まれます。 必要に応じて、コンポーネントを NavMenu
コンポーネントに追加し、そのコンポーネントへのリンクがアプリの UI ベースのナビゲーションに表示されるようにすることができます。
前の HelloWorld
コンポーネントでは、NavLink
コンポーネントを Shared
フォルダーの NavMenu
コンポーネントに追加できます。 NavLink
や NavMenu
コンポーネントの説明など詳しくは、「ASP.NET Core の Blazor ルーティングとナビゲーション」をご覧ください。
マークアップ
コンポーネントの UI は、Razor マークアップ、C#、HTML で構成される Razor 構文を使用して定義されます。 アプリがコンパイルされると、HTML マークアップと C# のレンダリング ロジックはコンポーネント クラスに変換されます。 生成されたクラスの名前は、ファイルの名前と一致します。
コンポーネント クラスのメンバーは、1 つまたは複数の @code
ブロック内で定義されます。 @code
ブロックでは、コンポーネントの状態が指定され、C# で処理されます。
- プロパティとフィールドの初期化子。
- 親コンポーネントとルート パラメーターによって渡される引数からのパラメーター値。
- ユーザー イベント処理、ライフサイクル イベント、およびカスタム コンポーネント ロジックのメソッド。
コンポーネント メンバーは、@
記号で始まる C# 式を使ったレンダリング ロジックで使用されます。 たとえば、フィールド名の前に @
を付けることによって、C# フィールドがレンダリングされます。 次の Markup
コンポーネントでは、以下を評価およびレンダリングします。
- 見出し要素の CSS プロパティ値
headingFontStyle
のfont-style
。 - 見出し要素のコンテンツの
headingText
。
Pages/Markup.razor
:
@page "/markup"
<h1 style="font-style:@headingFontStyle">@headingText</h1>
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
}
注意
Blazor ドキュメント全体の例では、プライベート メンバーの private
アクセス修飾子を指定します。 プライベート メンバーは、コンポーネントのクラスにスコープ指定されます。 しかし、C# では、アクセス修飾子が存在しない場合、private
アクセス修飾子が想定されるため、独自のコードでメンバーを "private
" として明示的にマークすることは省略可能です。 アクセス修飾子の詳細については、「アクセス修飾子 (C# プログラミング ガイド)」を参照してください。
Blazor フレームワークでは、コンポーネントを内部的にレンダリング ツリーとして処理します。これは、コンポーネントのドキュメント オブジェクト モデル (DOM) とカスケード スタイル シートオブジェクト モデル (CSSOM) の組み合わせです。 コンポーネントが最初にレンダリングされた後に、そのコンポーネントのレンダリング ツリーがイベントに応じて再生成されます。 Blazor によって新旧のレンダリング ツリーが比較され、表示目的でブラウザーの DOM に変更がすべて適用されます。 詳しくは、「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。
コンポーネントは通常の C# クラスであり、プロジェクト内の任意の場所に配置できます。 Web ページを生成するコンポーネントは、通常、Pages
フォルダーに存在します。 ページ以外のコンポーネントは、多くの場合、Shared
フォルダー、またはプロジェクトに追加されたカスタム フォルダーに配置されます。
C# コントロール構造体、ディレクティブ、およびディレクティブ属性の Razor 構文は小文字です (例: @if
、@code
、@bind
)。 プロパティ名は大文字です (例: LayoutComponentBase.Body の @Body
)。
非同期メソッド (async
) では、void
を返すことはサポートされていません
Blazor フレームワークでは、void
を返す非同期メソッド (async
) は追跡されません。 その結果、void
が返された場合、例外はキャッチされません。 非同期メソッドからは、常に Task が返されます。
入れ子になったコンポーネント
コンポーネントには、HTML 構文を使用して宣言することで、他のコンポーネントを含めることができます。 コンポーネントを使うためのマークアップは、そのコンポーネントの種類をタグ名とする HTML タグのようになります。
他のコンポーネントで見出しを表示するために使用できる次の Heading
コンポーネントについて考えてみます。
Shared/Heading.razor
:
<h1 style="font-style:@headingFontStyle">Heading Example</h1>
@code {
private string headingFontStyle = "italic";
}
HeadingExample
コンポーネント内の次のマークアップでは、<Heading />
タグが表示される位置に、前の Heading
コンポーネントをレンダリングします。
Pages/HeadingExample.razor
:
@page "/heading-example"
<Heading />
同じ名前空間内のコンポーネント名と一致しない最初の文字が大文字の HTML 要素がコンポーネントに含まれている場合、要素に予期しない名前が付いていることを示す警告が出力されます。 コンポーネントの名前空間に @using
ディレクティブを追加すると、コンポーネントを使用できるようになり、警告が解決されます。 詳細については、「名前空間」セクションを参照してください。
このセクションに示されている Heading
コンポーネントの例には @page
ディレクティブがないため、Heading
コンポーネントには、ブラウザーで直接要求を使用してユーザーが直接アクセスすることはできません。 しかし、@page
ディレクティブを持つコンポーネントは、他のコンポーネントの入れ子にすることができます。 Razor ファイルの先頭に @page "/heading"
を含めることによって Heading
コンポーネントに直接アクセスできた場合は、/heading
と /heading-example
の両方でブラウザー要求に対してコンポーネントがレンダリングされます。
名前空間
一般に、コンポーネントの名前空間は、アプリのルート名前空間と、アプリ内のコンポーネントの場所 (フォルダー) から派生します。 アプリのルート名前空間が BlazorSample
で、Counter
コンポーネントが Pages
フォルダーに存在する場合:
Counter
コンポーネントの名前空間はBlazorSample.Pages
になります。- コンポーネントの完全修飾型名は
BlazorSample.Pages.Counter
になります。
コンポーネントを保持するカスタム フォルダーの場合は、@using
ディレクティブを親コンポーネントまたはアプリの _Imports.razor
ファイルに追加します。 次の例では、Components
フォルダー内のコンポーネントを使用できるようにします。
@using BlazorSample.Components
Note
_Imports.razor
ファイルの @using
ディレクティブは、C# ファイル (.cs
) ではなく Razor ファイル (.razor
) にのみ適用されます。
コンポーネントは、完全修飾名を使用して参照することもできます。この場合、@using
ディレクティブは必要ありません。 次の例では、アプリの Components
フォルダーで ProductDetail
コンポーネントを直接参照します。
<BlazorSample.Components.ProductDetail />
Razor で作成されるコンポーネントの名前空間は、(優先順に並んだ) 以下に基づきます。
- Razor ファイルのマークアップ内の
@namespace
ディレクティブ (@namespace BlazorSample.CustomNamespace
など)。 - プロジェクト ファイル内のプロジェクトの
RootNamespace
(<RootNamespace>BlazorSample</RootNamespace>
など)。 - プロジェクト ファイルのファイル名 (
.csproj
) から取得されたプロジェクト名、およびプロジェクト ルートからコンポーネントへのパス。 たとえば、フレームワークでは、プロジェクト名前空間がBlazorSample
(BlazorSample.csproj
) の{PROJECT ROOT}/Pages/Index.razor
は、Index
コンポーネントの名前空間BlazorSample.Pages
に解決されます。{PROJECT ROOT}
はプロジェクトのルート パスです。 コンポーネントは C# の名前のバインド規則に従います。 この例のIndex
コンポーネントの場合、スコープ内のコンポーネントは、次のすべてのコンポーネントです。- 同じフォルダー (
Pages
) に含まれるもの。 - 別の名前空間を明示的に指定しない、プロジェクトのルート内のコンポーネント。
- 同じフォルダー (
以下はサポートされていません。
global::
修飾。- 別名が付けられた
using
ステートメントによるコンポーネントのインポート。 たとえば、@using Foo = Bar
はサポートされていません。 - 部分修飾名。 たとえば、
@using BlazorSample
をコンポーネントに追加してから、アプリのShared
フォルダー (Shared/NavMenu.razor
) のNavMenu
コンポーネントを<Shared.NavMenu></Shared.NavMenu>
で参照することはできません。
部分クラスのサポート
コンポーネントは C# 部分クラスとして生成され、次のいずれかの方法を使用して作成されます。
- 1 つのファイルには、1 つまたは複数の
@code
ブロックで定義されている C# コード、HTML マークアップ、および Razor マークアップが含まれています。 Blazor プロジェクト テンプレートでは、この 1 つのファイルの方法を使用してコンポーネントを定義します。 - HTML および Razor マークアップは、Razor ファイル (
.razor
) に配置されます。 C# コードは、部分クラスとして定義されている分離コード ファイル (.cs
) に配置されます。
Note
コンポーネント固有のスタイルを定義するコンポーネント スタイル シートは、個別のファイル (.css
) です。 Blazor の CSS 分離については、「ASP.NET Core Blazor の CSS の分離」をご覧ください。
次の例は、Blazor プロジェクト テンプレートから生成されたアプリ内の @code
ブロックを含む既定の Counter
コンポーネントを示しています。 マークアップと C# コードは同じファイル内にあります。 これは、コンポーネントの作成で最も一般的に使用される方法です。
Pages/Counter.razor
:
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
次の Counter
コンポーネントでは、部分クラスで分離コード ファイルを使用して、C# コードから HTML と Razor マークアップを分割します。
Pages/CounterPartialClass.razor
:
@page "/counter-partial-class"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
Pages/CounterPartialClass.razor.cs
:
namespace BlazorSample.Pages
{
public partial class CounterPartialClass
{
private int currentCount = 0;
void IncrementCount()
{
currentCount++;
}
}
}
_Imports.razor
ファイルの @using
ディレクティブは、C# ファイル (.cs
) ではなく Razor ファイル (.razor
) にのみ適用されます。 必要に応じて、部分クラス ファイルに名前空間を追加します。
コンポーネントで使用される一般的な名前空間は次のとおりです。
using System.Net.Http;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using Microsoft.JSInterop;
一般的な名前空間には、アプリの名前空間と、そのアプリの Shared
フォルダーに対応する名前空間も含まれます。
using BlazorSample;
using BlazorSample.Shared;
基本クラスの指定
@inherits
ディレクティブは、コンポーネントの基本クラスを指定するために使用されます。 次の例は、コンポーネントで基本クラスを継承し、コンポーネントのプロパティとメソッドを提供する方法を示しています。 BlazorRocksBase
基本クラスは、ComponentBase から派生します。
Pages/BlazorRocks.razor
:
@page "/blazor-rocks"
@inherits BlazorRocksBase
<h1>@BlazorRocksText</h1>
BlazorRocksBase.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample
{
public class BlazorRocksBase : ComponentBase
{
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
}
}
コンポーネントのパラメーター
''コンポーネント パラメーター'' によりデータがコンポーネントに渡されます。これらのパラメーターは、[Parameter]
属性を指定したコンポーネント クラス上で、パブリック C# プロパティを使用して定義されます。 次の例では、組み込みの参照型 (System.String) とユーザー定義の参照型 (PanelBody
) がコンポーネント パラメーターとして渡されます。
PanelBody.cs
:
public class PanelBody
{
public string? Text { get; set; }
public string? Style { get; set; }
}
Shared/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"
};
}
警告
コンポーネント パラメーターの初期値の指定はサポートされていますが、コンポーネントが初めてレンダリングされた後に独自のパラメーターに書き込むコンポーネントは作成しないでください。 詳細については、この記事の「上書きされたパラメーター」セクションをご覧ください。
ParameterChild
コンポーネントの Title
および Body
コンポーネント パラメーターは、コンポーネントのインスタンスをレンダリングする HTML タグの引数によって設定されます。 次の ParameterParent
コンポーネントでは、2 つの ParameterChild
コンポーネントがレンダリングされます。
- 1 つ目の
ParameterChild
コンポーネントは、パラメーター引数を指定せずにレンダリングされます。 - 2 つ目の
ParameterChild
コンポーネントでは、明示的な C# 式を使用してPanelBody
のプロパティの値を設定するParameterParent
コンポーネントからのTitle
とBody
の値を受け取ります。
Pages/ParameterParent.razor
:
@page "/parameter-parent"
<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" })" />
ParameterParent
コンポーネントでコンポーネントのパラメーター値が指定されていない場合、ParameterParent
コンポーネントの次のレンダリングされた HTML マークアップに ParameterChild
コンポーネントの既定値が表示されます。 ParameterParent
コンポーネントでコンポーネントのパラメーター値が指定されている場合は、その ParameterChild
コンポーネントの既定値が置き換えられます。
Note
わかりやすくするために、レンダリングされた CSS スタイル クラスは次のレンダリングされた HTML マークアップには表示されていません。
<h1>Child component (without attribute values)</h1>
<div>
<div>Set By Child</div>
<div>Set by child.</div>
</div>
<h1>Child component (with attribute values)</h1>
<div>
<div>Set by Parent</div>
<div>Set by parent.</div>
</div>
Razor の予約済みの @
記号を使用して、メソッドの C# フィールド、プロパティ、または結果を HTML 属性値としてコンポーネント パラメーターに代入します。 次の ParameterParent2
コンポーネントでは、上記の ParameterChild
コンポーネントの 4 つのインスタンスが表示され、それらの Title
パラメーター値が次のように設定されます。
title
フィールドの値。GetTitle
C# メソッドの結果。- 暗黙的な C# 式を使用する ToLongDateString による長い形式での現在のローカル日付。
panelData
オブジェクトのTitle
プロパティ。
文字列パラメーターには @
プレフィックスが必要です。 そうしないと、フレームワークでは文字列リテラルが設定されているものと想定されます。
文字列パラメーターの外部では、厳密には必要ない場合でも、非リテラルに @
プレフィックスを使うことをお勧めします。
リテラル (ブール値など)、キーワード (this
など)、または null
には @
プレフィックスを使うことをお勧めしませんが、必要であれば使ってもかまいません。 たとえば、IsFixed="@true"
は一般的ではありませんがサポートされています。
HTML5 の仕様によると、ほとんどの場合、パラメーター属性値を囲む引用符は省略可能です。 たとえば、Value="this"
の代わりに Value=this
を使ってもかまいません。 ただし、覚えやすく、Web ベースのテクノロジで広く採用されているため、引用符を使うことをお勧めします。
このドキュメントのコード例では次にようになっています。
- 常に引用符を使用します。 例:
Value="this"
. - 非リテラルでは、省略可能な場合でも、常に
@
プレフィックスを使います。 例:Title="@title"
:title
は文字列型の変数です。Count="@ct"
:ct
は数値型の変数です。 - Razor 式の外部のリテラルでは、常に
@
を使いません。 例:IsFixed="true"
.
Pages/ParameterParent2.razor
:
@page "/parameter-parent-2"
<ParameterChild Title="@title" />
<ParameterChild Title="@GetTitle()" />
<ParameterChild Title="@DateTime.Now.ToLongDateString()" />
<ParameterChild Title="@panelData.Title" />
@code {
private string title = "From Parent field";
private PanelData panelData = new();
private string GetTitle()
{
return "From Parent method";
}
private class PanelData
{
public string Title { get; set; } = "From Parent object";
}
}
注意
C# メンバーをコンポーネント パラメーターに代入する場合は、メンバーにプレフィックスとして @
記号を付け、パラメーターの HTML 属性にはプレフィックスは付けません。
正しい:
<ParameterChild Title="@title" />
正しくない:
<ParameterChild @Title="title" />
Razor ページ (.cshtml
) とは異なり、Blazor では、コンポーネントのレンダリング中に Razor 式で非同期処理を実行することはできません。 これは、Blazor が対話型 UI をレンダリングするように設計されているためです。 対話型 UI の場合、画面には常に何かが表示されている必要があるため、レンダリング フローをブロックしても意味はありません。 代わりに、非同期処理は、いずれかの非同期ライフサイクル イベント中に実行されます。 非同期ライフサイクル イベントが発生するたびに、コンポーネントは再びレンダリングされる可能性があります。 次の Razor 構文はサポートされていません。
<ParameterChild Title="@await ..." />
上記の例に含まれるコードでは、アプリのビルド時に "コンパイラ エラー" が発生します。
'await' 演算子は、非同期メソッド内でのみ使用できます。 このメソッドを 'async' 修飾子でマークし、その戻り値の型を 'Task' に変更することを検討してください。
上記の例で非同期的に Title
パラメーターの値を取得するには、次の例に示すように、コンポーネントで OnInitializedAsync
ライフサイクル イベントを使用できます。
<ParameterChild Title="@title" />
@code {
private string? title;
protected override async Task OnInitializedAsync()
{
title = await ...;
}
}
詳しくは、「ASP.NET Core Razor コンポーネントのライフサイクル」をご覧ください。
パラメーターに代入するために、明示的な Razor 式を使用してテキストを式の結果と連結することは、サポートされていません。 次の例では、テキスト "Set by
" とオブジェクトのプロパティ値を連結しようとしています。 この構文は Razor ページ (.cshtml
) でサポートされていますが、コンポーネントで子の Title
パラメーターに代入する場合は無効です。 次の Razor 構文はサポートされていません。
<ParameterChild Title="Set by @(panelData.Title)" />
上記の例に含まれるコードでは、アプリのビルド時に "コンパイラ エラー" が発生します。
コンポーネント属性では、複合コンテンツ (C# とマークアップの混合) はサポートされていません。
合成値の割り当てをサポートするには、メソッド、フィールド、またはプロパティを使用します。 次の例では、C# メソッド GetTitle
で、"Set by
" とオブジェクトのプロパティ値の連結を実行します。
Pages/ParameterParent3.razor
:
@page "/parameter-parent-3"
<ParameterChild Title="@GetTitle()" />
@code {
private PanelData panelData = new();
private string GetTitle() => $"Set by {panelData.Title}";
private class PanelData
{
public string Title { get; set; } = "Parent";
}
}
詳細については、「ASP.NET Coreの Razor 構文リファレンス」を参照してください。
警告
コンポーネント パラメーターの初期値の指定はサポートされていますが、コンポーネントが初めてレンダリングされた後に独自のパラメーターに書き込むコンポーネントは作成しないでください。 詳細については、この記事の「上書きされたパラメーター」セクションをご覧ください。
コンポーネント パラメーターは "自動プロパティ" として宣言する必要があります。つまり、それらの get
や set
アクセサーにカスタム ロジックを含めることはできません。 たとえば、次の StartData
プロパティは自動プロパティです。
[Parameter]
public DateTime StartData { get; set; }
get
または set
アクセサーにカスタム ロジックを配置しないでください。なぜなら、コンポーネント パラメーターの目的は、親コンポーネントから子コンポーネントに情報をフローさせるためのチャネルとして使用することだけだからです。 子コンポーネントのプロパティの set
アクセサーに、親コンポーネントの再レンダリングが発生する原因となるロジックが含まれている場合、レンダリングの無限ループが発生します。
受け取ったパラメーター値を変換するには、次のようにします。
- パラメーター プロパティは、指定された生データを表す自動プロパティのままにしておきます。
- 異なるプロパティまたはメソッドを作成し、パラメーター プロパティに基づいて変換されたデータを指定します。
OnParametersSetAsync
をオーバーライドし、新しいデータを受け取るたびに受け取ったパラメーターを変換します。
コンポーネント パラメーターに初期値を書き込むことはサポートされています。これは、初期値の代入によって、Blazor の自動コンポーネント レンダリングが妨げられることはないからです。 DateTime.Now での現在のローカル DateTime の StartData
への次の代入は、コンポーネントで有効な構文です。
[Parameter]
public DateTime StartData { get; set; } = DateTime.Now;
DateTime.Now の初期代入後は、開発者コードで StartData
に値を代入しないでください。 詳細については、この記事の「上書きされたパラメーター」セクションをご覧ください。
[EditorRequired]
属性を適用して、必要なコンポーネント パラメーターを指定します。 パラメーター値が指定されていない場合、エディターまたはビルド ツールによってユーザーに警告が表示される場合があります。 この属性は、[Parameter]
属性でもマークされているプロパティでのみ有効です。 EditorRequiredAttribute は、デザイン時とアプリのビルド時に適用されます。 この属性は実行時に適用されません。また、null
以外のパラメーター値は保証されません。
[Parameter]
[EditorRequired]
public string? Title { get; set; }
単一行属性リストもサポートされています。
[Parameter, EditorRequired]
public string? Title { get; set; }
Tuples
(API ドキュメント) は、コンポーネントのパラメーターと RenderFragment
型に対してサポートされています。 次のコンポーネント パラメーターの例では、Tuple
で 3 つの値を渡します。
Shared/RenderTupleChild.razor
:
<div class="card w-50" style="margin-bottom:15px">
<div class="card-header font-weight-bold"><code>Tuple</code> 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 Tuple<int, string, bool>? Data { get; set; }
}
Pages/RenderTupleParent.razor
:
@page "/render-tuple-parent"
<h1>Render <code>Tuple</code> Parent</h1>
<RenderTupleChild Data="@data" />
@code {
private Tuple<int, string, bool> data = new(999, "I aim to misbehave.", true);
}
C# 7.0 以降の Razor コンポーネントでは、"名前のないタプル" のみがサポートされます。 Razor コンポーネントでの名前付きタプルのサポートは、ASP.NET Core の今後のリリースで計画されています。 詳しくは、「Blazor Transpiler issue with named Tuples (dotnet/aspnetcore #28982) (名前付きタプルに関する Blazor トランスパイラーのイシュー (dotnet/aspnetcore #28982))」をご覧ください。
引用 ©2005 Universal Pictures: Serenity (Nathan Fillion)
ルート パラメーター
コンポーネントでは、@page
ディレクティブのルート テンプレートでルート パラメーターを指定できます。 Blazor ルーターでは、ルート パラメーターを使用して、対応するコンポーネント パラメーターが設定されます。
省略可能なルート パラメーターがサポートされています。 次の例では、省略可能なパラメーター text
を使用して、ルート セグメントの値をコンポーネントの Text
プロパティに割り当てます。 セグメントが存在しない場合、Text
の値は OnInitialized
ライフサイクル メソッドで "fantastic
" に設定されます。
Pages/RouteParameter.razor
:
@page "/route-parameter/{text?}"
<h1>Blazor is @Text!</h1>
@code {
[Parameter]
public string? Text { get; set; }
protected override void OnInitialized()
{
Text = Text ?? "fantastic";
}
}
複数のフォルダー境界を越えるパスをキャプチャするキャッチオール ルート パラメーター ({*pageRoute}
) について詳しくは、「ASP.NET Core の Blazor ルーティングとナビゲーション」をご覧ください。
子コンテンツのレンダリング フラグメント
コンポーネントでは、別のコンポーネントのコンテンツを設定できます。 代入コンポーネントでは、子コンポーネントの開始および終了タグの間にコンテンツを指定します。
次の例では、RenderFragmentChild
コンポーネントに、RenderFragment としてレンダリングする UI のセグメントを表す ChildContent
コンポーネント パラメーターがあります。 コンポーネントの Razor マークアップ内の ChildContent
の位置は、コンテンツが最終的な HTML 出力にレンダリングされる場所です。
Shared/RenderFragmentChild.razor
:
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Child content</div>
<div class="card-body">@ChildContent</div>
</div>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
重要
RenderFragment コンテンツを受け取るプロパティは、規則によって ChildContent
という名前にする必要があります。
イベント コールバックは、RenderFragment ではサポートされていません。
次の RenderFragmentParent
コンポーネントでは、子コンポーネントの開始および終了タグ内にコンテンツを配置することによって RenderFragmentChild
をレンダリングするためのコンテンツを指定します。
Pages/RenderFragmentParent.razor
:
@page "/render-fragment-parent"
<h1>Render child content</h1>
<RenderFragmentChild>
Content of the child component is supplied
by the parent component.
</RenderFragmentChild>
Blazor による子コンテンツのレンダリング方法により、for
ループ内のコンポーネントのレンダリングでは、インクリメントするループ変数が RenderFragmentChild
コンポーネントのコンテンツ内で使用されている場合、ローカル インデックス変数が必要になります。 次の例は、前の RenderFragmentParent
コンポーネントに追加できます。
<h1>Three children with an index variable</h1>
@for (int c = 0; c < 3; c++)
{
var current = c;
<RenderFragmentChild>
Count: @current
</RenderFragmentChild>
}
または、for
ループではなく、Enumerable.Range と共に foreach
ループを使用します。 次の例は、前の RenderFragmentParent
コンポーネントに追加できます。
<h1>Second example of three children with an index variable</h1>
@foreach (var c in Enumerable.Range(0,3))
{
<RenderFragmentChild>
Count: @c
</RenderFragmentChild>
}
レンダリング フラグメントは、Blazor アプリ全体で子コンテンツをレンダリングするために使用され、次の記事と記事のセクションの例と共に説明されています。
注意
Blazor フレームワークの組み込み Razor コンポーネントでは、それぞれ同じ ChildContent
コンポーネント パラメーター規則を使用してコンテンツを設定します。 API ドキュメントでコンポーネント パラメーター プロパティ名ChildContent
を検索することにより、子コンテンツを設定しているコンポーネントを確認できます (検索用語 "保育" を使用して API をフィルター処理)。
再利用可能なレンダリング ロジックのフラグメントをレンダリングする
レンダリング ロジックを再利用するための方法として、子コンポーネントを純粋に取り出すことができます。 任意のコンポーネントの @code
ブロックで、RenderFragment を定義し、必要に応じて任意の場所からフラグメントをレンダリングします。
<h1>Hello, world!</h1>
@RenderWelcomeInfo
<p>Render the welcome info a second time:</p>
@RenderWelcomeInfo
@code {
private RenderFragment RenderWelcomeInfo = __builder =>
{
<p>Welcome to your new app!</p>
};
}
詳細については、レンダリング ロジックの再利用に関するページを参照してください。
上書きされたパラメーター
Blazor フレームワークでは、一般に安全な親から子へのパラメーターの割り当てが行われます。
- パラメーターが予期せずに上書きされることはありません。
- 副作用は最小限に抑えられます。 たとえば、追加のレンダリングは、無限のレンダリング ループが作成される可能性があるため、回避されます。
子コンポーネントは、親コンポーネントのレンダリング時に既存の値を上書きする可能性がある新しいパラメーター値を受け取ります。 子コンポーネントでパラメーター値が誤って上書きされることは、1 つまたは複数のデータバインド パラメーターを使用してコンポーネントを開発しており、開発者が子のパラメーターに直接書き込む場合に多く発生します。
- 子コンポーネントは、親コンポーネントの 1 つまたは複数のパラメーター値を使用してレンダリングされます。
- 子によって、パラメーターの値が直接書き込まれます。
- 親コンポーネントがレンダリングされ、子のパラメーターの値が上書きされます。
パラメーター値の上書きの可能性は、子コンポーネントのプロパティ set
アクセサーにも及びます。
重要
一般的なガイダンスとして、コンポーネントが初めてレンダリングされた後に、独自のパラメーターに直接書き込むコンポーネントを作成しないでください。
次が実行される Expander
コンポーネントについて考えてみましょう。
- 子コンテンツのレンダリング。
- コンポーネント パラメーター (
Expanded
) を使用した、子コンテンツの表示の切り替え。
次の Expander
コンポーネントで上書きされるパラメーターを示した後に、このシナリオの正しい方法を示すために変更した Expander
コンポーネントを紹介します。 説明されている動作を体験するために、次の例をローカルのサンプル アプリで使用することができます。
Shared/Expander.razor
:
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
<div class="card-body">
<h2 class="card-title">Toggle (<code>Expanded</code> = @Expanded)</h2>
@if (Expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>
@code {
[Parameter]
public bool Expanded { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
private void Toggle()
{
Expanded = !Expanded;
}
}
Expander
コンポーネントは、StateHasChanged を呼び出す可能性のある次の ExpanderExample
親コンポーネントに追加されます。
- 開発者コードで StateHasChanged を呼び出すと、その状態が変更されたことがコンポーネントに通知され、通常は UI を更新するためにコンポーネントの再レンダリングがトリガーされます。 StateHasChanged について詳しくは、「ASP.NET Core Razor コンポーネントのライフサイクル」と「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。
- ボタンの
@onclick
ディレクティブ属性により、イベント ハンドラーがそのボタンのonclick
イベントにアタッチされます。 イベント処理について詳しくは、「ASP.NET Core Blazor のイベント処理」をご覧ください。
Pages/ExpanderExample.razor
:
@page "/expander-example"
<Expander Expanded="true">
Expander 1 content
</Expander>
<Expander Expanded="true" />
<button @onclick="StateHasChanged">
Call StateHasChanged
</button>
初期状態では、Expanded
プロパティが切り替えられると、Expander
コンポーネントはそれぞれ独立して動作します。 子コンポーネントの状態は、想定どおりのままです。
親コンポーネント内で StateHasChanged が呼び出されると、それらのパラメーターが変更された可能性がある場合に、Blazor フレームワークは子コンポーネントを再レンダリングします。
- Blazor が明示的に確認するパラメーター型のグループに対して、パラメーターのいずれかが変更されたことを Blazor が検出した場合、子コンポーネントを再レンダリングします。
- 確認されていないパラメーター型に対しては、"パラメーターが変更されたかどうかに関係なく"、Blazor は子コンポーネントを再レンダリングします。 子コンテンツは、このカテゴリのパラメーター型に分類されます。子コンテンツは、他の変更可能なオブジェクトを参照するデリゲートである型 RenderFragment だからです。
ExpanderExample
コンポーネントの場合:
- 1 つ目の
Expander
コンポーネントで、潜在的に変更可能な RenderFragment 内の子コンテンツを設定します。そのため、親コンポーネント内で StateHasChanged を呼び出すと、自動的にコンポーネントが再レンダリングされ、Expanded
の値がその初期値であるtrue
に上書きされる可能性があります。 - 2 つ目の
Expander
コンポーネントでは、子コンテンツを設定しません。 そのため、潜在的に変更可能な RenderFragment は存在しません。 親コンポーネントで StateHasChanged を呼び出しても、子コンポーネントは自動的に再レンダリングされないので、コンポーネントのExpanded
の値は上書きされません。
前のシナリオでの状態を維持するには、Expander
コンポーネントで "プライベート フィールド" を使用して、切り替え状態を維持します。
次の変更された Expander
コンポーネント:
- 親から
Expanded
コンポーネント パラメーター値を受け入れます。 - コンポーネント パラメーター値を、
OnInitialized
イベントの "プライベート フィールド" (expanded
) に割り当てます。 - プライベート フィールドを使用して、その内部のトグル状態を維持します。これは、パラメーターに直接書き込まれないようにする方法を示しています。
Note
このセクションのアドバイスは、コンポーネント パラメーター set
アクセサーの同様のロジックに及ぶため、同様の望ましくない副作用が発生する可能性があります。
Shared/Expander.razor
:
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
<div class="card-body">
<h2 class="card-title">Toggle (<code>expanded</code> = @expanded)</h2>
@if (expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>
@code {
private bool expanded;
[Parameter]
public bool Expanded { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
{
expanded = Expanded;
}
private void Toggle()
{
expanded = !expanded;
}
}
双方向の親子バインディングの例については、「ASP.NET Core Blazor データ バインディング」をご覧ください。 詳細については、Blazor両方向のバインド エラー (dotnet/aspnetcore #24599) に関するページを参照してください。
Blazor によって確認される正確な型の情報など、変更検出の詳細については、「ASP.NET Core Razor コンポーネントのレンダリング」を参照してください。
属性スプラッティングと任意のパラメーター
コンポーネントでは、コンポーネントの宣言されたパラメーターに加えて、追加の属性をキャプチャしてレンダリングできます。 追加の属性は、ディクショナリに取り込んでから、@attributes
Razor ディレクティブ属性を使用してコンポーネントがレンダリングされるときに要素に "スプラッティング" できます。 このシナリオは、さまざまなカスタマイズをサポートするマークアップ要素を生成するコンポーネントを定義する場合に便利です。 たとえば、多くのパラメーターをサポートする <input>
に対して、属性を個別に定義するのは面倒な場合があります。
次の Splat
コンポーネントでは、以下のことを行います。
- 1 つ目の
<input>
要素 (id="useIndividualParams"
) では、個々のコンポーネント パラメーターを使用します。 - 2 つ目の
<input>
要素 (id="useAttributesDict"
) では、属性のスプラッティングを使用します。
Pages/Splat.razor
:
@page "/splat"
<input id="useIndividualParams"
maxlength="@maxlength"
placeholder="@placeholder"
required="@required"
size="@size" />
<input id="useAttributesDict"
@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" }
};
}
Web ページにレンダリングされる <input>
要素は同じです。
<input id="useIndividualParams"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">
<input id="useAttributesDict"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">
任意の属性を受け入れるには、CaptureUnmatchedValues プロパティを true
に設定したコンポーネント パラメーターを定義します。
@code {
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object>? InputAttributes { get; set; }
}
[Parameter]
の CaptureUnmatchedValues プロパティにより、パラメーターを他のパラメーターと一致しないすべての属性と一致させることができます。 1 つのコンポーネントで、CaptureUnmatchedValues を持つパラメーターは 1 つだけ定義できます。 CaptureUnmatchedValues で使用されるプロパティの型は、文字列キーを使用して Dictionary<string, object>
から割り当て可能である必要があります。 このシナリオでは、IEnumerable<KeyValuePair<string, object>>
または IReadOnlyDictionary<string, object>
も使用できます。
要素属性の位置を基準とした @attributes
の位置は重要です。 @attributes
が要素にスプラッティングされると、属性は右から左 (最後から最初) に処理されます。 子コンポーネントを使用する次の親コンポーネントの例を考えてみます。
Shared/AttributeOrderChild1.razor
:
<div @attributes="AdditionalAttributes" extra="5" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}
Pages/AttributeOrderParent1.razor
:
@page "/attribute-order-parent-1"
<AttributeOrderChild1 extra="10" />
AttributeOrderChild1
コンポーネントの extra
属性が @attributes
の右側に設定されています。 属性は右から左 (最後から最初) に処理されるため、追加の属性によって渡された場合に、AttributeOrderParent1
コンポーネントのレンダリングされる <div>
に、extra="5"
が含まれます。
<div extra="5" />
次の例では、子コンポーネントの <div>
で extra
と @attributes
の順序が逆になります。
Shared/AttributeOrderChild2.razor
:
<div extra="5" @attributes="AdditionalAttributes" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}
Pages/AttributeOrderParent2.razor
:
@page "/attribute-order-parent-2"
<AttributeOrderChild2 extra="10" />
追加の属性によって渡された場合に、親コンポーネントのレンダリングされる Web ページの <div>
には extra="10"
が含まれます。
<div extra="10" />
コンポーネントへの参照をキャプチャする
コンポーネント参照を使用すると、コマンドを発行するためのコンポーネント インスタンスを参照することができます。 コンポーネント参照をキャプチャするには:
- 子コンポーネントに
@ref
属性を追加します。 - 子コンポーネントと同じ型のフィールドを定義します。
コンポーネントがレンダリングされると、フィールドにコンポーネント インスタンスが設定されます。 その後、そのインスタンスに対して .NET メソッドを呼び出すことができます。
ChildMethod
が呼び出されたときにメッセージをログに記録する次の ReferenceChild
コンポーネントについて考えてみます。
Shared/ReferenceChild.razor
:
@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> logger
@code {
public void ChildMethod(int value)
{
logger.LogInformation("Received {Value} in ChildMethod", value);
}
}
コンポーネント参照は、コンポーネントがレンダリングされた後にのみ設定され、その出力には ReferenceChild
要素が含まれます。 コンポーネントがレンダリングされるまで、参照するものはありません。
コンポーネントのレンダリングが終了した後にコンポーネント参照を操作するには、OnAfterRender
または OnAfterRenderAsync
メソッドを使用します。
イベント ハンドラーで参照変数を使用するには、ラムダ式を使用するか、OnAfterRender
または OnAfterRenderAsync
メソッドでイベント ハンドラー デリゲートを割り当てます。 これにより、イベント ハンドラーが割り当てられる前に参照変数が確実に割り当てられます。
次のラムダ手法では、前の ReferenceChild
コンポーネントを使用します。
Pages/ReferenceParent1.razor
:
@page "/reference-parent-1"
<button @onclick="@(() => childComponent?.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild? childComponent;
}
次のデリゲート手法では、上記の ReferenceChild
コンポーネントを使用します。
Pages/ReferenceParent2.razor
:
@page "/reference-parent-2"
<button @onclick="@(() => callChildMethod?.Invoke())">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild? childComponent;
private Action? callChildMethod;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
callChildMethod = CallChildMethod;
}
}
private void CallChildMethod()
{
childComponent?.ChildMethod(5);
}
}
コンポーネント参照の取り込みでは、要素参照の取り込みと同様の構文を使用しますが、コンポーネント参照の取り込みは JavaScript 相互運用機能ではありません。 コンポーネント参照は、JavaScript コードに渡されません。 コンポーネント参照は、.NET コードでのみ使用されます。
重要
子コンポーネントの状態を変えるためにコンポーネント参照を使用しないでください。 代わりに、通常の宣言型コンポーネント パラメーターを使用して、子コンポーネントにデータを渡します。 コンポーネント パラメーターを使用すると、子コンポーネントが正しいタイミングで自動的にレンダリングされます。 詳しくは、「コンポーネントのパラメーター」セクションと、記事「ASP.NET Core Blazor データ バインディング」をご覧ください。
同期コンテキスト
Blazor では、同期コンテキスト (SynchronizationContext) を使用して、1 つの実行の論理スレッドを強制します。 コンポーネントのライフサイクル メソッドと、Blazor によって発生するイベント コールバックは、同期コンテキストで実行されます。
Blazor Server の同期コンテキストでは、ブラウザーの WebAssembly モデル (シングル スレッド) と厳密に一致するように、シングルスレッド環境のエミュレートが試行されます。 どの時点でも、作業は 1 つのスレッドでのみ実行され、1 つの論理スレッドであるという印象になります。 2 つの操作が同時に実行されることはありません。
スレッドをブロックする呼び出しを避ける
一般に、コンポーネントでは次のメソッドは呼び出さないでください。 次のメソッドでは実行スレッドがブロックされます。そのため、基になる Task が完了するまで、アプリの動作が再開されなくなります。
Note
このセクションに示されているスレッド ブロック メソッドを使用する Blazor ドキュメントの例では、推奨されるコーディング ガイダンスとしてではなく、デモンストレーション目的でのみメソッドを使用しています。 たとえば、いくつかのコンポーネント コードのデモでは、Thread.Sleep を呼び出して、実行時間の長いプロセスをシミュレートします。
状態を更新するために外部でコンポーネント メソッドを呼び出す
タイマーやその他の通知などの外部イベントに基づいてコンポーネントを更新する必要がある場合は、InvokeAsync
メソッドを使用します。これにより、Blazor の同期コンテキストにコードの実行がディスパッチされます。 たとえば、リッスンしているコンポーネントに、更新状態について通知できる次の ''通知サービス'' を考えてみます。 Update
メソッドは、アプリ内のどこからでも呼び出すことができます。
TimerService.cs
:
public class TimerService : IDisposable
{
private int elapsedCount;
private readonly static TimeSpan heartbeatTickRate = TimeSpan.FromSeconds(5);
private readonly ILogger<TimerService> logger;
private readonly NotifierService notifier;
private PeriodicTimer? timer;
public TimerService(NotifierService notifier,
ILogger<TimerService> logger)
{
this.notifier = notifier;
this.logger = logger;
}
public async Task Start()
{
if (timer is null)
{
timer = new(heartbeatTickRate);
logger.LogInformation("Started");
using (timer)
{
while (await timer.WaitForNextTickAsync())
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation($"elapsedCount: {elapsedCount}");
}
}
}
}
public void Dispose()
{
timer?.Dispose();
}
}
NotifierService.cs
:
public class NotifierService
{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}
public event Func<string, int, Task>? Notify;
}
サービスを登録します。
Blazor WebAssembly アプリでは、
Program.cs
のシングルトンとしてサービスを登録します。builder.Services.AddSingleton<NotifierService>(); builder.Services.AddSingleton<TimerService>();
Blazor Server アプリでは、
Program.cs
を範囲としてサービスを登録します。builder.Services.AddScoped<NotifierService>(); builder.Services.AddScoped<TimerService>();
NotifierService
を使用して、コンポーネントを更新します。
Pages/ReceiveNotifications.razor
:
@page "/receive-notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer
<h1>Receive Notifications</h1>
<h2>Timer Service</h2>
<button @onclick="StartTimer">Start Timer</button>
<h2>Notifications</h2>
<p>
Status:
@if (lastNotification.key is not null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting first notification</span>
}
</p>
@code {
private (string key, int value) lastNotification;
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public async Task OnNotify(string key, int value)
{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}
private async Task StartTimer()
{
await Timer.Start();
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
前の例の場合:
- Blazor の同期コンテキスト外で
NotifierService
からコンポーネントのOnNotify
メソッドが呼び出されます。InvokeAsync
を使用して、正しいコンテキストに切り替え、レンダリングをキューに登録します。 詳しくは、「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。 - コンポーネントでは IDisposable を実装します。
OnNotify
デリゲートのサブスクライブはDispose
メソッドで解除されます。このメソッドは、コンポーネントが破棄されたときにフレームワークによって呼び出されます。 詳しくは、「ASP.NET Core Razor コンポーネントのライフサイクル」をご覧ください。
要素またはコンポーネントの保持の制御での @key
の使用
要素またはコンポーネントのリストをレンダリングし、その後に要素またはコンポーネントが変更された場合、Blazor では、前のどの要素やコンポーネントを保持できるか、およびモデル オブジェクトをそれらにどのようにマップするかを決定する必要があります。 通常、このプロセスは自動で、無視できますが、プロセスの制御が必要になる場合があります。
次の Details
および People
コンポーネントについて考えてみます。
Details
コンポーネントでは、<input>
要素に表示される親のPeople
コンポーネントからデータ (Data
) を受け取ります。 表示される任意の<input>
要素では、<input>
要素のいずれかを選択すると、ユーザーからページのフォーカスを受け取ることができます。People
コンポーネントでは、Details
コンポーネントを使用して表示する person オブジェクトのリストを作成します。 3 秒ごとに、新しい個人がコレクションに追加されます。
このデモで次のことを行うことができます。
- レンダリングされた複数の
Details
コンポーネントの中から<input>
を選択する。 - people コレクションの自動拡大時のページのフォーカスの動作を調べる。
Shared/Details.razor
:
<input value="@Data" />
@code {
[Parameter]
public string? Data { get; set; }
}
次の People
コンポーネントでは、個人を OnTimerCallback
に追加する各イテレーションによって、Blazor でコレクション全体がリビルドされます。 ページのフォーカスは、<input>
要素の ''同じインデックス'' 位置に留まります。そのため、個人が追加されるたびにフォーカスが移動します。 ''ユーザーが選択した内容からフォーカスを移動することは、望ましい動作ではありません。 '' 次のコンポーネントでの不適切な動作を示した後、ユーザーのエクスペリエンスを向上させるために @key
ディレクティブ属性が使用されます。
Pages/People.razor
:
@page "/people"
@using System.Timers
@implements IDisposable
@foreach (var person in people)
{
<Details Data="@person.Data" />
}
@code {
private Timer timer = new Timer(3000);
public List<Person> people =
new()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
});
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
public class Person
{
public string? Data { get; set; }
}
}
people
コレクションのコンテンツは、挿入、削除、または順序変更されたエントリによって変更されます。 再レンダリングによって、表示動作に違いが生じる可能性があります。 people
コレクションに個人が挿入されるたびに、現在フォーカスがある要素の ''前の要素'' でフォーカスを受け取ります。 ユーザーのフォーカスは失われます。
要素またはコンポーネントのコレクションへのマッピング プロセスは、@key
ディレクティブ属性を使用して制御できます。 @key
を使用すると、キーの値に基づいて要素またはコンポーネントが確実に保持されます。 前の例の Details
コンポーネントが person
項目にキー指定されている場合、Blazor では、変更されていない Details
コンポーネントのレンダリングが無視されます。
people
コレクションで @key
ディレクティブ属性を使用するように People
コンポーネントを変更するには、<Details>
要素を次のように更新します。
<Details @key="person" Data="@person.Data" />
people
コレクションが変更されても、Details
インスタンスと person
インスタンス間の関連付けは保持されます。 コレクションの先頭に Person
が挿入されると、その対応する位置に 1 つの新しい Details
インスタンスが挿入されます。 他のインスタンスは変更されません。 そのため、コレクションに人々が追加されても、ユーザーのフォーカスは失われません。
@key
ディレクティブ属性が使用されている場合、他のコレクションの更新でも同じ動作になります。
- コレクションからインスタンスが削除された場合、対応するコンポーネント インスタンスのみが UI から除去されます。 他のインスタンスは変更されません。
- コレクション エントリの順序が変更された場合、対応するコンポーネント インスタンスは UI で保持され、順序が変更されます。
重要
キーは、各コンテナー要素やコンポーネントに対してローカルです。 キーはドキュメント全体でグローバルに比較されません。
どのようなときに @key
を使用するか
一般に、リストがレンダリングされ (たとえば、foreach
ブロックで)、@key
を定義するための適切な値が存在する場合は常に、@key
を使用することは意味があります。
次の例に示すように、@key
を使用して、オブジェクトが変更されない場合に要素またはコンポーネントのサブツリーを保持することもできます。
例 1:
<li @key="person">
<input value="@person.Data" />
</li>
例 2:
<div @key="person">
@* other HTML elements *@
</div>
person
インスタンスが変更された場合、@key
属性ディレクティブにより、Blazor に次のことが強制されます。
<li>
または<div>
の全体およびその子孫を破棄する。- 新しい要素とコンポーネントを使用して、UI 内でサブツリーをリビルドする。
これは、コレクションがサブツリー内で変更されたときに UI の状態が確実に保持されないようにするのに役立ちます。
@key
のスコープ
@key
属性のディレクティブのスコープは、その親内の自身の兄弟です。
次の例を考えてみます。 first
および second
キーは、外側の <div>
要素内の同じスコープで互いに比較されます。
<div>
<div @key="first">...</div>
<div @key="second">...</div>
</div>
次の例では、互いに関係がなく、互いに影響を与えることのない、独自のスコープの first
と second
キーを示しています。 各 @key
のスコープはその親 <div>
要素のみであり、各親 <div>
要素ではありません。
<div>
<div @key="first">...</div>
</div>
<div>
<div @key="second">...</div>
</div>
前に示した Details
コンポーネントの場合、次の例では、同じ @key
のスコープ内の person
データがレンダリングされます。これが @key
の一般的な使用例です。
<div>
@foreach (var person in people)
{
<Details @key="person" Data="@person.Data" />
}
</div>
@foreach (var person in people)
{
<div @key="person">
<Details Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li @key="person">
<Details Data="@person.Data" />
</li>
}
</ol>
次の例の @key
のスコープは、各 Details
コンポーネント インスタンスを囲む、<div>
または <li>
要素のみです。 つまり、people
コレクションの各メンバーの person
データは、レンダリングされた各 Details
コンポーネントの各 person
インスタンスのキーではありません。 @key
を使用する場合は、次のパターンを避けてください。
@foreach (var person in people)
{
<div>
<Details @key="person" Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li>
<Details @key="person" Data="@person.Data" />
</li>
}
</ol>
どのようなときに @key
を使用しないか
@key
でレンダリングすると、パフォーマンスが低下します。 パフォーマンスの低下は大きくありませんが、要素やコンポーネントを保持することによって、アプリにメリットがある場合にのみ @key
を指定してください。
@key
を使用しない場合でも、Blazor では可能な限り、子要素とコンポーネント インスタンスが保持されます。 @key
を使用する唯一の利点は、マッピングを選択する Blazor ではなく、保持されているコンポーネント インスタンスにモデル インスタンスをマップする "方法" が制御されることです。
@key
に使用する値
一般に、@key
には、次のいずれかの値を指定するのが適切です。
- モデル オブジェクト インスタンス。 たとえば、前の例では
Person
インスタンス (person
) が使用されていました。 これにより、オブジェクト参照の等価性に基づいて保持されます。 - 一意識別子。 たとえば、一意識別子は
int
、string
、またはGuid
型の主キー値を基にすることができます。
@key
に使用される値は競合しないようにしてください。 同じ親要素内で競合する値が検出された場合、Blazor では、古い要素やコンポーネントを新しい要素やコンポーネントに確定的にマップできないため、例外がスローされます。 個別の値 (オブジェクト インスタンスや主キー値など) のみを使用してください。
属性を適用する
属性は、@attribute
ディレクティブを使用してコンポーネントに適用できます。 次の例では、[Authorize]
属性をコンポーネントのクラスに適用しています。
@page "/"
@attribute [Authorize]
条件付き HTML 要素属性
HTML 要素属性プロパティは、.NET 値に基づいて条件付きで設定されます。 値が false
または null
の場合、プロパティは設定されません。 値が true
の場合は、プロパティが設定されます。
次の例では、IsCompleted
により、<input>
要素の checked
プロパティが設定されるかどうかが決定されます。
Pages/ConditionalAttribute.razor
:
@page "/conditional-attribute"
<label>
<input type="checkbox" checked="@IsCompleted" />
Is Completed?
</label>
<button @onclick="@(() => IsCompleted = !IsCompleted)">
Change IsCompleted
</button>
@code {
[Parameter]
public bool IsCompleted { get; set; }
}
詳細については、「ASP.NET Coreの Razor 構文リファレンス」を参照してください。
警告
.NET 型が bool
の場合、aria-pressed
などの一部の HTML 属性が正しく機能しません。 そのような場合は、bool
ではなく string
型を使用します。
生 HTML
通常、文字列は DOM テキスト ノードを使用してレンダリングされます。つまり、それらに含まれている可能性のあるすべてのマークアップが無視され、リテラル テキストとして扱われます。 生 HTML をレンダリングするには、HTML コンテンツを MarkupString 値にラップします。 値は HTML または SVG として解析され、DOM に挿入されます。
警告
信頼されていないソースから構築された生 HTML をレンダリングすることは、セキュリティ リスクであるため、常に避ける必要があります。
次の例では、MarkupString 型を使用して、コンポーネントのレンダリングされた出力に静的 HTML コンテンツのブロックを追加しています。
Pages/MarkupStringExample.razor
:
@page "/markup-string-example"
@((MarkupString)myMarkup)
@code {
private string myMarkup =
"<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
}
Razor テンプレート
レンダリング フラグメントは、UI スニペットを定義するための Razor テンプレート構文を使用して定義できます。 Razor テンプレートでは次の形式を使用します。
@<{HTML tag}>...</{HTML tag}>
次の例では、RenderFragment と RenderFragment<TValue> の値を指定し、コンポーネント内にテンプレートを直接レンダリングする方法を示しています。 レンダリング フラグメントは、引数としてテンプレート コンポーネントに渡すこともできます。
Pages/RazorTemplate.razor
:
@page "/razor-template"
@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; }
}
}
前のコードのレンダリングされた結果:
<p>The time is 4/19/2021 8:54:46 AM.</p>
<p>Pet: Nutty Rex</p>
静的な資産
静的資産の場合、Blazor では ASP.NET Core アプリの規則に従います。 静的資産は、プロジェクトの web root
(wwwroot
) フォルダー、または wwwroot
フォルダーの下のフォルダーにあります。
静的アセットの Web ルートを参照するには、ベース相対パス (/
) を使用します。 次の例では、logo.png
が物理的に {PROJECT ROOT}/wwwroot/images
フォルダーに配置されています。 {PROJECT ROOT}
は、アプリのプロジェクト ルートです。
<img alt="Company logo" src="/images/logo.png" />
コンポーネントでは、チルダ スラッシュ表記 (~/
) はサポートされていません。
アプリのベース パスの設定について詳しくは、「ASP.NET Core Blazor のホストと展開」をご覧ください。
タグ ヘルパーはコンポーネントでサポートされない
Tag Helpers
はコンポーネントでサポートされていません。 Blazor にタグ ヘルパーのような機能を提供するには、タグ ヘルパーと同じ機能を持つコンポーネントを作成し、代わりにそのコンポーネントを使用します。
スケーラブル ベクター グラフィックス (SVG) イメージ
Blazor は HTML をレンダリングするため、スケーラブル ベクター グラフィックス (SVG) 画像 (.svg
) などのブラウザーでサポートされている画像は、<img>
タグを介してサポートされます。
<img alt="Example image" src="image.svg" />
同様に、SVG 画像は、スタイルシート ファイル (.css
) の CSS 規則でサポートされています。
.element-class {
background-image: url("image.svg");
}
SVG 内の任意の HTML の表示に Blazor は <foreignObject>
要素をサポートしています。 このマークアップでは、任意の HTML、RenderFragment、または Razor コンポーネントを表すことができます。
その具体的な例を次に示します。
string
の表示 (@message
)。<input>
要素とvalue
フィールドを使用した双方向のバインディング。Robot
コンポーネント。
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" rx="10" ry="10" width="200" height="200" stroke="black"
fill="none" />
<foreignObject x="20" y="20" width="160" height="160">
<p>@message</p>
</foreignObject>
</svg>
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="200" height="200">
<label>
Two-way binding:
<input @bind="value" @bind:event="oninput" />
</label>
</foreignObject>
</svg>
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject>
<Robot />
</foreignObject>
</svg>
@code {
private string message = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
"elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
private string? value;
}
空白文字のレンダリング動作
@preservewhitespace
ディレクティブが true
の値と共に使用されている場合を除き、既定では、次の場合に余分な空白が削除されます。
- 要素内の先頭または末尾。
- RenderFragment/RenderFragment<TValue> パラメーター内の先頭または末尾 (たとえば、別のコンポーネントに渡された子コンテンツ)。
@if
または@foreach
のような、C# コード ブロックの前か後にある。
空白文字の削除は、white-space: pre
などの CSS ルールを使用するときに、レンダリングされた出力に影響を与えることがあります。 このパフォーマンスの最適化を無効にして、空白を保持するには、次のいずれかの操作を実行します。
- Razor ファイル (
.razor
) の先頭に@preservewhitespace true
ディレクティブを追加し、特定のコンポーネントに設定を適用する。 _Imports.razor
ファイル内に@preservewhitespace true
ディレクティブを追加し、サブディレクトリまたはプロジェクト全体に設定を適用する。
ほとんどの場合、アプリでは一般的に通常の動作が続行されるため (ただし、速くなります)、何の措置も必要ありません。 空白文字を削除すると特定のコンポーネントでレンダリングの問題が発生する場合は、そのコンポーネントで @preservewhitespace true
を使用し、この最適化を無効にします。
ジェネリック型パラメーターのサポート
@typeparam
ディレクティブによって、生成されるコンポーネント クラスのジェネリック型パラメーターを宣言します。
@typeparam TItem
where
型制約を含む C# 構文がサポートされています。
@typeparam TEntity where TEntity : IEntity
次の例では、ListGenericTypeItems1
コンポーネントは TExample
としてジェネリック型に指定されます。
Shared/ListGenericTypeItems1.razor
:
@typeparam TExample
@if (ExampleList is not null)
{
<ul>
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}
@code {
[Parameter]
public IEnumerable<TExample>? ExampleList{ get; set; }
}
次の GenericTypeExample1
コンポーネントでは、2 つの ListGenericTypeItems1
コンポーネントがレンダリングされます。
- 文字列または整数のデータは、各コンポーネントの
ExampleList
パラメーターに割り当てられます。 - 割り当てられたデータの型に一致する型
string
またはint
が、各コンポーネントの型パラメーター (TExample
) に設定されます。
Pages/GenericTypeExample1.razor
:
@page "/generic-type-example-1"
<h1>Generic Type Example 1</h1>
<ListGenericTypeItems1 ExampleList="@(new List<string> { "Item 1", "Item 2" })"
TExample="string" />
<ListGenericTypeItems1 ExampleList="@(new List<int> { 1, 2, 3 })"
TExample="int" />
詳細については、「ASP.NET Coreの Razor 構文リファレンス」を参照してください。 テンプレート化されたコンポーネントを使用したジェネリック型指定の例については、「ASP.NET Core Blazor テンプレート コンポーネント」を参照してください。
カスケードされたジェネリック型のサポート
先祖コンポーネントでは、[CascadingTypeParameter]
属性を使用して、型パラメーターを名前で子孫にカスケードさせることができます。 この属性を使用すると、ジェネリック型の推定で、指定された型パラメーターを、同じ名前の型パラメーターを持つ子孫と共に自動的に使用できます。
コンポーネントに @attribute [CascadingTypeParameter(...)]
を追加すると、指定されたジェネリック型引数は、次のような子孫によって自動的に使用されます。
- 同じ
.razor
ドキュメント内のコンポーネントの子コンテンツとして入れ子になっている。 - まったく同じ名前で
@typeparam
を宣言している。 - 型パラメーターに対して、明示的に指定される、または暗黙的に推定される別の値を持たないでください。 別の値を指定するか推定した場合は、カスケードされたジェネリック型よりも優先されます。
カスケードされた型パラメーターを受け取ると、コンポーネントは名前が一致する CascadingTypeParameterAttribute を持つ最も近い先祖からパラメーター値を取得します。 カスケードされたジェネリック型パラメーターは、特定のサブツリー内でオーバーライドされます。
照合は名前によってのみ実行されます。 そのため、T
や TItem
などの一般的な名前を持つカスケードされたジェネリック型パラメーターは使用しないことをお勧めします。 開発者が型パラメーターをカスケードすることを選択した場合、関連性のないコンポーネントからの別のカスケードされた型パラメーターと競合しないほど十分に名前が固有であることが暗黙的に期待されます。
ジェネリック型は、先祖 (親) コンポーネントを使用して、次のいずれかの方法で子コンポーネントにカスケードできます。これは、次の 2 つのサブセクションに示されます。
- カスケードされたジェネリック型を明示的に設定します。
- カスケードされたジェネリック型を推定します。
次のサブセクションでは、次の 2 つの ListDisplay
コンポーネントを使用した上記の方法の例を示します。 コンポーネントは、リスト データを受け取ってレンダリングし、TExample
としてジェネリック型に指定されます。 これらのコンポーネントはデモンストレーションが目的であり、リストがレンダリングされるテキストの色のみ異なります。 ローカル テスト アプリで次のサブセクションのコンポーネントを試す場合は、最初に次の 2 つのコンポーネントをアプリに追加します。
Shared/ListDisplay1.razor
:
@typeparam TExample
@if (ExampleList is not null)
{
<ul style="color:blue">
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}
@code {
[Parameter]
public IEnumerable<TExample>? ExampleList { get; set; }
}
Shared/ListDisplay2.razor
:
@typeparam TExample
@if (ExampleList is not null)
{
<ul style="color:red">
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}
@code {
[Parameter]
public IEnumerable<TExample>? ExampleList { get; set; }
}
先祖コンポーネントに基づいた明示的なジェネリック型
このセクションのデモでは、TExample
の型を明示的にカスケードします。
Note
このセクションでは、「カスケードされたジェネリック型のサポート」セクションの 2 つの ListDisplay
コンポーネントを使用します。
次の ListGenericTypeItems2
コンポーネントでは、データを受け取り、TExample
という名前のジェネリック型パラメーターをその子孫コンポーネントにカスケードします。 次の親コンポーネントでは、ListGenericTypeItems2
コンポーネントを使用して、前の ListDisplay
コンポーネントでリスト データを表示します。
Shared/ListGenericTypeItems2.razor
:
@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample
<h2>List Generic Type Items 2</h2>
@ChildContent
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
次の GenericTypeExample2
親コンポーネントは、子コンポーネントにカスケードされる ListGenericTypeItems2
型 (TExample
) を指定する 2 つの ListGenericTypeItems2
コンポーネントの子コンテンツ (RenderFragment) を設定します。 ListDisplay
コンポーネントは、例に示されているリスト項目データと一緒にレンダリングされます。 文字列データは 1 番目の ListGenericTypeItems2
コンポーネントで使用され、整数データは 2 番目のListGenericTypeItems2
コンポーネントで使用されます。
Pages/GenericTypeExample2.razor
:
@page "/generic-type-example-2"
<h1>Generic Type Example 2</h1>
<ListGenericTypeItems2 TExample="string">
<ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })" />
<ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })" />
</ListGenericTypeItems2>
<ListGenericTypeItems2 TExample="int">
<ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" />
<ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" />
</ListGenericTypeItems2>
次のデモに示すように、型を明示的に指定すると、値とパラメーターのカスケードを使用して子コンポーネントにデータを提供することもできます。
Shared/ListDisplay3.razor
:
@typeparam TExample
@if (ExampleList is not null)
{
<ul style="color:blue">
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}
@code {
[CascadingParameter]
protected IEnumerable<TExample>? ExampleList { get; set; }
}
Shared/ListDisplay4.razor
:
@typeparam TExample
@if (ExampleList is not null)
{
<ul style="color:red">
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}
@code {
[CascadingParameter]
protected IEnumerable<TExample>? ExampleList { get; set; }
}
Shared/ListGenericTypeItems3.razor
:
@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample
<h2>List Generic Type Items 3</h2>
@ChildContent
@if (ExampleList is not null)
{
<ul style="color:green">
@foreach(var item in ExampleList)
{
<li>@item</li>
}
</ul>
<p>
Type of <code>TExample</code>: @typeof(TExample)
</p>
}
@code {
[CascadingParameter]
protected IEnumerable<TExample>? ExampleList { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
次の例のデータをカスケードする場合は、型を ListGenericTypeItems3
コンポーネントに提供する必要があります。
Pages/GenericTypeExample3.razor
:
@page "/generic-type-example-3"
<h1>Generic Type Example 3</h1>
<CascadingValue Value="@stringData">
<ListGenericTypeItems3 TExample="string">
<ListDisplay3 />
<ListDisplay4 />
</ListGenericTypeItems3>
</CascadingValue>
<CascadingValue Value="@integerData">
<ListGenericTypeItems3 TExample="int">
<ListDisplay3 />
<ListDisplay4 />
</ListGenericTypeItems3>
</CascadingValue>
@code {
private List<string> stringData = new() { "Item 1", "Item 2" };
private List<int> integerData = new() { 1, 2, 3 };
}
複数のジェネリック型をカスケードする場合は、セット内のすべてのジェネリック型の値を渡す必要があります。 次の例では、TItem
、TValue
、TEdit
は GridColumn
ジェネリック型ですが、GridColumn
を配置する親コンポーネントは TItem
型を指定していません。
<GridColumn TValue="string" TEdit="@TextEdit" />
上記の例では、GridColumn
コンポーネントに TItem
型パラメーターがないというコンパイル時のエラーが生成されます。 有効なコードでは、すべての型を指定します。
<GridColumn TValue="string" TEdit="@TextEdit" TItem="@User" />
先祖コンポーネントに基づいてジェネリック型を推定する
このセクションのデモでは、TExample
に対して推定された型をカスケードします。
Note
このセクションでは、「カスケードされたジェネリック型のサポート」セクションの 2 つの ListDisplay
コンポーネントを使用します。
Shared/ListGenericTypeItems4.razor
:
@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample
<h2>List Generic Type Items 4</h2>
@ChildContent
@if (ExampleList is not null)
{
<ul style="color:green">
@foreach(var item in ExampleList)
{
<li>@item</li>
}
</ul>
<p>
Type of <code>TExample</code>: @typeof(TExample)
</p>
}
@code {
[Parameter]
public IEnumerable<TExample>? ExampleList { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
カスケードされた型が推定される次の GenericTypeExample4
コンポーネントは、表示用に異なるデータを提供します。
Pages/GenericTypeExample4.razor
:
@page "/generic-type-example-4"
<h1>Generic Type Example 4</h1>
<ListGenericTypeItems4 ExampleList="@(new List<string> { "Item 5", "Item 6" })">
<ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })" />
<ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })" />
</ListGenericTypeItems4>
<ListGenericTypeItems4 ExampleList="@(new List<int> { 7, 8, 9 })">
<ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" />
<ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" />
</ListGenericTypeItems4>
カスケードされた型が推定される次の GenericTypeExample5
コンポーネントは、表示用に同じデータを提供します。 次の例では、データをコンポーネントに直接割り当てます。
Pages/GenericTypeExample5.razor
:
@page "/generic-type-example-5"
<h1>Generic Type Example 5</h1>
<ListGenericTypeItems4 ExampleList="@stringData">
<ListDisplay1 ExampleList="@stringData" />
<ListDisplay2 ExampleList="@stringData" />
</ListGenericTypeItems4>
<ListGenericTypeItems4 ExampleList="@integerData">
<ListDisplay1 ExampleList="@integerData" />
<ListDisplay2 ExampleList="@integerData" />
</ListGenericTypeItems4>
@code {
private List<string> stringData = new() { "Item 1", "Item 2" };
private List<int> integerData = new() { 1, 2, 3 };
}
JavaScript から Razor コンポーネントをレンダリングする
Razor コンポーネントは、既存の JavaScript (JS) アプリの JS から動的にレンダリングできます。
Razor から JS コンポーネントをレンダリングするには、コンポーネントを JS レンダリングのルート コンポーネントとして登録し、コンポーネントに識別子を割り当てる必要があります。
Blazor Server アプリで、
Program.cs
内の AddServerSideBlazor への呼び出しを変更します。builder.Services.AddServerSideBlazor(options => { options.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter"); });
Note
上記のコード例では、
Program.cs
ファイル内のアプリのコンポーネント (たとえば、using BlazorSample.Pages;
) の名前空間が必要です。Blazor WebAssembly アプリで、
Program.cs
内の RootComponents で RegisterForJavaScript を呼び出します。builder.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter");
Note
上記のコード例では、
Program.cs
ファイル内のアプリのコンポーネント (たとえば、using BlazorSample.Pages;
) の名前空間が必要です。
JS アプリ (blazor.server.js
または blazor.webassembly.js
) に Blazor を読み込みます。 登録された識別子を使用して JS からコンテナー要素にコンポーネントをレンダリングし、必要に応じてコンポーネント パラメーターを渡します。
let containerElement = document.getElementById('my-counter');
await Blazor.rootComponents.add(containerElement, 'counter', { incrementAmount: 10 });
Blazor カスタム要素
Microsoft.AspNetCore.Components.CustomElements
NuGet パッケージを使用してカスタム要素を構築する場合は、"実験的" サポートを利用できます。 カスタム要素では、標準の HTML インターフェイスを使用してカスタム HTML 要素を実装します。
警告
試験段階の機能は、機能の有効性を調べる目的で提供されており、安定バージョンには含まれていない場合があります。
ルート コンポーネントをカスタム要素として登録します。
Blazor Server アプリで、
Program.cs
内の AddServerSideBlazor への呼び出しを変更します。builder.Services.AddServerSideBlazor(options => { options.RootComponents.RegisterAsCustomElement<Counter>("my-counter"); });
Note
上記のコード例では、
Program.cs
ファイル内のアプリのコンポーネント (たとえば、using BlazorSample.Pages;
) の名前空間が必要です。Blazor WebAssembly アプリで、
Program.cs
内の RootComponents でRegisterAsCustomElement
を呼び出します。builder.RootComponents.RegisterAsCustomElement<Counter>("my-counter");
Note
上記のコード例では、
Program.cs
ファイル内のアプリのコンポーネント (たとえば、using BlazorSample.Pages;
) の名前空間が必要です。
アプリの HTML の Blazor スクリプト タグの "前" に、次の <script>
タグを含めます。
<script src="/_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomElements.js"></script>
任意の Web フレームワークでカスタム要素を使用します。 たとえば、上記のカウンター カスタム要素は、次のマークアップと共に React アプリで使用されます。
<my-counter increment-amount={incrementAmount}></my-counter>
Blazor を使用してカスタム要素を作成する方法の完全な例については、Blazor カスタム要素のサンプル プロジェクトに関するページを参照してください。
警告
カスタム要素機能は現在試験段階で、サポートされていないため、いつでも変更または削除される可能性があります。 この特定のアプローチが要件をどの程度満たすかについてフィードバックをお寄せください。
Angular コンポーネントと React コンポーネントを生成する
Angular や React などの Web フレームワークの Razor コンポーネントから、フレームワーク固有の JavaScript (JS) コンポーネントを生成します。 この機能は .NET 6 には含まれていませんが、JS から Razor コンポーネントをレンダリングする新しいサポートによって有効になります。 GitHub の JS コンポーネント生成サンプルは、Razor コンポーネントから Angular コンポーネントと React コンポーネントを生成する方法を示しています。 詳細については、GitHub サンプル アプリの README.md
ファイルを参照してください。
警告
Angular コンポーネントと React コンポーネントの機能は現在試験段階で、サポートされていないため、いつでも変更または削除される可能性があります。 この特定のアプローチが要件をどの程度満たすかについてフィードバックをお寄せください。
Blazor アプリは、"Razor コンポーネント" (非公式には "Blazor コンポーネント" と呼ばれます) を使って構築されます。 コンポーネントは、動的な動作を有効にするための処理ロジックを使用するユーザー インターフェイス (UI) の自己完結型の部分です。 コンポーネントは、入れ子にしたり、再利用したり、プロジェクト間で共有したり、MVC および Razor ページ アプリで使用したりすることができます。
コンポーネント クラス
コンポーネントは、.razor
ファイル拡張子の Razor コンポーネント ファイルで C# と HTML マークアップの組み合わせを使用して実装されます。
Razor の構文
コンポーネントでは Razor 構文を使用します。 コンポーネント、''ディレクティブ'' および ''ディレクティブ属性'' では、2 つの Razor 機能が広く使用されています。 これらは、Razor マークアップに表示される @
というプレフィックスが付いた予約キーワードです。
- ディレクティブ: コンポーネント マークアップの解析方法または関数を変更します。 たとえば、
@page
ディレクティブでは、ルート テンプレートでルーティング可能なコンポーネントを指定します。このディレクティブには、ブラウザーの特定の URL でのユーザーの要求により直接到達できます。 - ディレクティブ属性: コンポーネント要素の解析方法または関数を変更します。 たとえば、
<input>
要素の@bind
ディレクティブ属性では、データをその要素の値にバインドします。
コンポーネントで使用されるディレクティブとディレクティブ属性については、この記事および Blazor ドキュメント セットの他の記事で詳しく説明されています。 Razor の構文の一般的な情報については、「ASP.NET Coreの Razor 構文リファレンス」をご覧ください。
名前
コンポーネントの名前は大文字で始める必要があります。
ProductDetail.razor
は有効です。productDetail.razor
が無効です。
Blazor ドキュメント全体で使用される共通の Blazor の名前付け規則は次のとおりです。
- コンポーネント ファイルのパスでは、パスカル ケース†が使用され、コンポーネントのコード例が示される前に表示されます。 パスは一般的なフォルダーの場所を示します。 たとえば、
Pages/ProductDetail.razor
は、ProductDetail
コンポーネントのファイル名がProductDetail.razor
で、そのコンポーネントがアプリのPages
フォルダーに存在することを示します。 - ルーティング可能なコンポーネントのコンポーネント ファイル パスは、コンポーネントのルート テンプレート内の単語間のスペースに表示されるハイフン付きの URL に一致します。 たとえば、ルート テンプレートが
/product-detail
(@page "/product-detail"
) のProductDetail
コンポーネントは、ブラウザーの相対 URL/product-detail
で要求されます。
† パスカル ケース (大文字のキャメル ケース) は、スペースと句読点を使用せず、大文字の各単語の最初の文字 (最初の単語を含む) を使用する名前付け規則です。
ルーティング
Blazor でのルーティングは、@page
ディレクティブを使用するアプリ内のアクセス可能な各コンポーネントへのルート テンプレートを提供することで実現します。 @page
ディレクティブを含む Razor ファイルがコンパイルされると、生成されたクラスに、ルート テンプレートを指定する RouteAttribute が指定されます。 実行時に、ルーターによって RouteAttribute を持つコンポーネント クラスが検索され、要求された URL に一致するルート テンプレートを使用するコンポーネントがレンダリングされます。
次の HelloWorld
コンポーネントでは、/hello-world
のルート テンプレートを使用します。 コンポーネントのレンダリングされた Web ページには、相対 URL /hello-world
で到達できます。 既定のプロトコル、ホスト、およびポートを使用して Blazor アプリをローカルで実行する場合、HelloWorld
コンポーネントはブラウザーの https://localhost:5001/hello-world
で要求されます。 Web ページを生成するコンポーネントは通常、Pages
フォルダーに存在しますが、入れ子になったフォルダー内などのコンポーネントを保持するために任意のフォルダーを使用できます。
Pages/HelloWorld.razor
:
@page "/hello-world"
<h1>Hello World!</h1>
コンポーネントをアプリの UI ナビゲーションに追加するかどうかに関係なく、前のコンポーネントはブラウザーの /hello-world
で読み込まれます。 必要に応じて、コンポーネントを NavMenu
コンポーネントに追加し、そのコンポーネントへのリンクがアプリの UI ベースのナビゲーションに表示されるようにすることができます。
前の HelloWorld
コンポーネントでは、NavLink
コンポーネントを Shared
フォルダーの NavMenu
コンポーネントに追加できます。 NavLink
や NavMenu
コンポーネントの説明など詳しくは、「ASP.NET Core の Blazor ルーティングとナビゲーション」をご覧ください。
マークアップ
コンポーネントの UI は、Razor マークアップ、C#、HTML で構成される Razor 構文を使用して定義されます。 アプリがコンパイルされると、HTML マークアップと C# のレンダリング ロジックはコンポーネント クラスに変換されます。 生成されたクラスの名前は、ファイルの名前と一致します。
コンポーネント クラスのメンバーは、1 つまたは複数の @code
ブロック内で定義されます。 @code
ブロックでは、コンポーネントの状態が指定され、C# で処理されます。
- プロパティとフィールドの初期化子。
- 親コンポーネントとルート パラメーターによって渡される引数からのパラメーター値。
- ユーザー イベント処理、ライフサイクル イベント、およびカスタム コンポーネント ロジックのメソッド。
コンポーネント メンバーは、@
記号で始まる C# 式を使ったレンダリング ロジックで使用されます。 たとえば、フィールド名の前に @
を付けることによって、C# フィールドがレンダリングされます。 次の Markup
コンポーネントでは、以下を評価およびレンダリングします。
- 見出し要素の CSS プロパティ値
headingFontStyle
のfont-style
。 - 見出し要素のコンテンツの
headingText
。
Pages/Markup.razor
:
@page "/markup"
<h1 style="font-style:@headingFontStyle">@headingText</h1>
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
}
注意
Blazor ドキュメント全体の例では、プライベート メンバーの private
アクセス修飾子を指定します。 プライベート メンバーは、コンポーネントのクラスにスコープ指定されます。 しかし、C# では、アクセス修飾子が存在しない場合、private
アクセス修飾子が想定されるため、独自のコードでメンバーを "private
" として明示的にマークすることは省略可能です。 アクセス修飾子の詳細については、「アクセス修飾子 (C# プログラミング ガイド)」を参照してください。
Blazor フレームワークでは、コンポーネントを内部的にレンダリング ツリーとして処理します。これは、コンポーネントのドキュメント オブジェクト モデル (DOM) とカスケード スタイル シートオブジェクト モデル (CSSOM) の組み合わせです。 コンポーネントが最初にレンダリングされた後に、そのコンポーネントのレンダリング ツリーがイベントに応じて再生成されます。 Blazor によって新旧のレンダリング ツリーが比較され、表示目的でブラウザーの DOM に変更がすべて適用されます。 詳しくは、「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。
コンポーネントは通常の C# クラスであり、プロジェクト内の任意の場所に配置できます。 Web ページを生成するコンポーネントは、通常、Pages
フォルダーに存在します。 ページ以外のコンポーネントは、多くの場合、Shared
フォルダー、またはプロジェクトに追加されたカスタム フォルダーに配置されます。
C# コントロール構造体、ディレクティブ、およびディレクティブ属性の Razor 構文は小文字です (例: @if
、@code
、@bind
)。 プロパティ名は大文字です (例: LayoutComponentBase.Body の @Body
)。
非同期メソッド (async
) では、void
を返すことはサポートされていません
Blazor フレームワークでは、void
を返す非同期メソッド (async
) は追跡されません。 その結果、void
が返された場合、例外はキャッチされません。 非同期メソッドからは、常に Task が返されます。
入れ子になったコンポーネント
コンポーネントには、HTML 構文を使用して宣言することで、他のコンポーネントを含めることができます。 コンポーネントを使うためのマークアップは、そのコンポーネントの種類をタグ名とする HTML タグのようになります。
他のコンポーネントで見出しを表示するために使用できる次の Heading
コンポーネントについて考えてみます。
Shared/Heading.razor
:
<h1 style="font-style:@headingFontStyle">Heading Example</h1>
@code {
private string headingFontStyle = "italic";
}
HeadingExample
コンポーネント内の次のマークアップでは、<Heading />
タグが表示される位置に、前の Heading
コンポーネントをレンダリングします。
Pages/HeadingExample.razor
:
@page "/heading-example"
<Heading />
同じ名前空間内のコンポーネント名と一致しない最初の文字が大文字の HTML 要素がコンポーネントに含まれている場合、要素に予期しない名前が付いていることを示す警告が出力されます。 コンポーネントの名前空間に @using
ディレクティブを追加すると、コンポーネントを使用できるようになり、警告が解決されます。 詳細については、「名前空間」セクションを参照してください。
このセクションに示されている Heading
コンポーネントの例には @page
ディレクティブがないため、Heading
コンポーネントには、ブラウザーで直接要求を使用してユーザーが直接アクセスすることはできません。 しかし、@page
ディレクティブを持つコンポーネントは、他のコンポーネントの入れ子にすることができます。 Razor ファイルの先頭に @page "/heading"
を含めることによって Heading
コンポーネントに直接アクセスできた場合は、/heading
と /heading-example
の両方でブラウザー要求に対してコンポーネントがレンダリングされます。
名前空間
一般に、コンポーネントの名前空間は、アプリのルート名前空間と、アプリ内のコンポーネントの場所 (フォルダー) から派生します。 アプリのルート名前空間が BlazorSample
で、Counter
コンポーネントが Pages
フォルダーに存在する場合:
Counter
コンポーネントの名前空間はBlazorSample.Pages
になります。- コンポーネントの完全修飾型名は
BlazorSample.Pages.Counter
になります。
コンポーネントを保持するカスタム フォルダーの場合は、@using
ディレクティブを親コンポーネントまたはアプリの _Imports.razor
ファイルに追加します。 次の例では、Components
フォルダー内のコンポーネントを使用できるようにします。
@using BlazorSample.Components
Note
_Imports.razor
ファイルの @using
ディレクティブは、C# ファイル (.cs
) ではなく Razor ファイル (.razor
) にのみ適用されます。
コンポーネントは、完全修飾名を使用して参照することもできます。この場合、@using
ディレクティブは必要ありません。 次の例では、アプリの Components
フォルダーで ProductDetail
コンポーネントを直接参照します。
<BlazorSample.Components.ProductDetail />
Razor で作成されるコンポーネントの名前空間は、(優先順に並んだ) 以下に基づきます。
- Razor ファイルのマークアップ内の
@namespace
ディレクティブ (@namespace BlazorSample.CustomNamespace
など)。 - プロジェクト ファイル内のプロジェクトの
RootNamespace
(<RootNamespace>BlazorSample</RootNamespace>
など)。 - プロジェクト ファイルのファイル名 (
.csproj
) から取得されたプロジェクト名、およびプロジェクト ルートからコンポーネントへのパス。 たとえば、フレームワークでは、プロジェクト名前空間がBlazorSample
(BlazorSample.csproj
) の{PROJECT ROOT}/Pages/Index.razor
は、Index
コンポーネントの名前空間BlazorSample.Pages
に解決されます。{PROJECT ROOT}
はプロジェクトのルート パスです。 コンポーネントは C# の名前のバインド規則に従います。 この例のIndex
コンポーネントの場合、スコープ内のコンポーネントは、次のすべてのコンポーネントです。- 同じフォルダー (
Pages
) に含まれるもの。 - 別の名前空間を明示的に指定しない、プロジェクトのルート内のコンポーネント。
- 同じフォルダー (
以下はサポートされていません。
global::
修飾。- 別名が付けられた
using
ステートメントによるコンポーネントのインポート。 たとえば、@using Foo = Bar
はサポートされていません。 - 部分修飾名。 たとえば、
@using BlazorSample
をコンポーネントに追加してから、アプリのShared
フォルダー (Shared/NavMenu.razor
) のNavMenu
コンポーネントを<Shared.NavMenu></Shared.NavMenu>
で参照することはできません。
部分クラスのサポート
コンポーネントは C# 部分クラスとして生成され、次のいずれかの方法を使用して作成されます。
- 1 つのファイルには、1 つまたは複数の
@code
ブロックで定義されている C# コード、HTML マークアップ、および Razor マークアップが含まれています。 Blazor プロジェクト テンプレートでは、この 1 つのファイルの方法を使用してコンポーネントを定義します。 - HTML および Razor マークアップは、Razor ファイル (
.razor
) に配置されます。 C# コードは、部分クラスとして定義されている分離コード ファイル (.cs
) に配置されます。
Note
コンポーネント固有のスタイルを定義するコンポーネント スタイル シートは、個別のファイル (.css
) です。 Blazor の CSS 分離については、「ASP.NET Core Blazor の CSS の分離」をご覧ください。
次の例は、Blazor プロジェクト テンプレートから生成されたアプリ内の @code
ブロックを含む既定の Counter
コンポーネントを示しています。 マークアップと C# コードは同じファイル内にあります。 これは、コンポーネントの作成で最も一般的に使用される方法です。
Pages/Counter.razor
:
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
次の Counter
コンポーネントでは、部分クラスで分離コード ファイルを使用して、C# コードから HTML と Razor マークアップを分割します。
Pages/CounterPartialClass.razor
:
@page "/counter-partial-class"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
Pages/CounterPartialClass.razor.cs
:
namespace BlazorSample.Pages
{
public partial class CounterPartialClass
{
private int currentCount = 0;
void IncrementCount()
{
currentCount++;
}
}
}
_Imports.razor
ファイルの @using
ディレクティブは、C# ファイル (.cs
) ではなく Razor ファイル (.razor
) にのみ適用されます。 必要に応じて、部分クラス ファイルに名前空間を追加します。
コンポーネントで使用される一般的な名前空間は次のとおりです。
using System.Net.Http;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using Microsoft.JSInterop;
一般的な名前空間には、アプリの名前空間と、そのアプリの Shared
フォルダーに対応する名前空間も含まれます。
using BlazorSample;
using BlazorSample.Shared;
基本クラスの指定
@inherits
ディレクティブは、コンポーネントの基本クラスを指定するために使用されます。 次の例は、コンポーネントで基本クラスを継承し、コンポーネントのプロパティとメソッドを提供する方法を示しています。 BlazorRocksBase
基本クラスは、ComponentBase から派生します。
Pages/BlazorRocks.razor
:
@page "/blazor-rocks"
@inherits BlazorRocksBase
<h1>@BlazorRocksText</h1>
BlazorRocksBase.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample
{
public class BlazorRocksBase : ComponentBase
{
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
}
}
コンポーネントのパラメーター
''コンポーネント パラメーター'' によりデータがコンポーネントに渡されます。これらのパラメーターは、[Parameter]
属性を指定したコンポーネント クラス上で、パブリック C# プロパティを使用して定義されます。 次の例では、組み込みの参照型 (System.String) とユーザー定義の参照型 (PanelBody
) がコンポーネント パラメーターとして渡されます。
PanelBody.cs
:
public class PanelBody
{
public string Text { get; set; }
public string Style { get; set; }
}
Shared/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"
};
}
警告
コンポーネント パラメーターの初期値の指定はサポートされていますが、コンポーネントが初めてレンダリングされた後に独自のパラメーターに書き込むコンポーネントは作成しないでください。 詳細については、この記事の「上書きされたパラメーター」セクションをご覧ください。
ParameterChild
コンポーネントの Title
および Body
コンポーネント パラメーターは、コンポーネントのインスタンスをレンダリングする HTML タグの引数によって設定されます。 次の ParameterParent
コンポーネントでは、2 つの ParameterChild
コンポーネントがレンダリングされます。
- 1 つ目の
ParameterChild
コンポーネントは、パラメーター引数を指定せずにレンダリングされます。 - 2 つ目の
ParameterChild
コンポーネントでは、明示的な C# 式を使用してPanelBody
のプロパティの値を設定するParameterParent
コンポーネントからのTitle
とBody
の値を受け取ります。
Pages/ParameterParent.razor
:
@page "/parameter-parent"
<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" })" />
ParameterParent
コンポーネントでコンポーネントのパラメーター値が指定されていない場合、ParameterParent
コンポーネントの次のレンダリングされた HTML マークアップに ParameterChild
コンポーネントの既定値が表示されます。 ParameterParent
コンポーネントでコンポーネントのパラメーター値が指定されている場合は、その ParameterChild
コンポーネントの既定値が置き換えられます。
Note
わかりやすくするために、レンダリングされた CSS スタイル クラスは次のレンダリングされた HTML マークアップには表示されていません。
<h1>Child component (without attribute values)</h1>
<div>
<div>Set By Child</div>
<div>Set by child.</div>
</div>
<h1>Child component (with attribute values)</h1>
<div>
<div>Set by Parent</div>
<div>Set by parent.</div>
</div>
Razor の予約済みの @
記号を使用して、メソッドの C# フィールド、プロパティ、または結果を HTML 属性値としてコンポーネント パラメーターに代入します。 次の ParameterParent2
コンポーネントでは、上記の ParameterChild
コンポーネントの 4 つのインスタンスが表示され、それらの Title
パラメーター値が次のように設定されます。
title
フィールドの値。GetTitle
C# メソッドの結果。- 暗黙的な C# 式を使用する ToLongDateString による長い形式での現在のローカル日付。
panelData
オブジェクトのTitle
プロパティ。
文字列パラメーターには @
プレフィックスが必要です。 そうしないと、フレームワークでは文字列リテラルが設定されているものと想定されます。
文字列パラメーターの外部では、厳密には必要ない場合でも、非リテラルに @
プレフィックスを使うことをお勧めします。
リテラル (ブール値など)、キーワード (this
など)、または null
には @
プレフィックスを使うことをお勧めしませんが、必要であれば使ってもかまいません。 たとえば、IsFixed="@true"
は一般的ではありませんがサポートされています。
HTML5 の仕様によると、ほとんどの場合、パラメーター属性値を囲む引用符は省略可能です。 たとえば、Value="this"
の代わりに Value=this
を使ってもかまいません。 ただし、覚えやすく、Web ベースのテクノロジで広く採用されているため、引用符を使うことをお勧めします。
このドキュメントのコード例では次にようになっています。
- 常に引用符を使用します。 例:
Value="this"
. - 非リテラルでは、省略可能な場合でも、常に
@
プレフィックスを使います。 例:Title="@title"
:title
は文字列型の変数です。Count="@ct"
:ct
は数値型の変数です。 - Razor 式の外部のリテラルでは、常に
@
を使いません。 例:IsFixed="true"
.
Pages/ParameterParent2.razor
:
@page "/parameter-parent-2"
<ParameterChild Title="@title" />
<ParameterChild Title="@GetTitle()" />
<ParameterChild Title="@DateTime.Now.ToLongDateString()" />
<ParameterChild Title="@panelData.Title" />
@code {
private string title = "From Parent field";
private PanelData panelData = new();
private string GetTitle()
{
return "From Parent method";
}
private class PanelData
{
public string Title { get; set; } = "From Parent object";
}
}
注意
C# メンバーをコンポーネント パラメーターに代入する場合は、メンバーにプレフィックスとして @
記号を付け、パラメーターの HTML 属性にはプレフィックスは付けません。
正しい:
<ParameterChild Title="@title" />
正しくない:
<ParameterChild @Title="title" />
Razor ページ (.cshtml
) とは異なり、Blazor では、コンポーネントのレンダリング中に Razor 式で非同期処理を実行することはできません。 これは、Blazor が対話型 UI をレンダリングするように設計されているためです。 対話型 UI の場合、画面には常に何かが表示されている必要があるため、レンダリング フローをブロックしても意味はありません。 代わりに、非同期処理は、いずれかの非同期ライフサイクル イベント中に実行されます。 非同期ライフサイクル イベントが発生するたびに、コンポーネントは再びレンダリングされる可能性があります。 次の Razor 構文はサポートされていません。
<ParameterChild Title="@await ..." />
上記の例に含まれるコードでは、アプリのビルド時に "コンパイラ エラー" が発生します。
'await' 演算子は、非同期メソッド内でのみ使用できます。 このメソッドを 'async' 修飾子でマークし、その戻り値の型を 'Task' に変更することを検討してください。
上記の例で非同期的に Title
パラメーターの値を取得するには、次の例に示すように、コンポーネントで OnInitializedAsync
ライフサイクル イベントを使用できます。
<ParameterChild Title="@title" />
@code {
private string title;
protected override async Task OnInitializedAsync()
{
title = await ...;
}
}
詳しくは、「ASP.NET Core Razor コンポーネントのライフサイクル」をご覧ください。
パラメーターに代入するために、明示的な Razor 式を使用してテキストを式の結果と連結することは、サポートされていません。 次の例では、テキスト "Set by
" とオブジェクトのプロパティ値を連結しようとしています。 この構文は Razor ページ (.cshtml
) でサポートされていますが、コンポーネントで子の Title
パラメーターに代入する場合は無効です。 次の Razor 構文はサポートされていません。
<ParameterChild Title="Set by @(panelData.Title)" />
上記の例に含まれるコードでは、アプリのビルド時に "コンパイラ エラー" が発生します。
コンポーネント属性では、複合コンテンツ (C# とマークアップの混合) はサポートされていません。
合成値の割り当てをサポートするには、メソッド、フィールド、またはプロパティを使用します。 次の例では、C# メソッド GetTitle
で、"Set by
" とオブジェクトのプロパティ値の連結を実行します。
Pages/ParameterParent3.razor
:
@page "/parameter-parent-3"
<ParameterChild Title="@GetTitle()" />
@code {
private PanelData panelData = new();
private string GetTitle() => $"Set by {panelData.Title}";
private class PanelData
{
public string Title { get; set; } = "Parent";
}
}
詳細については、「ASP.NET Coreの Razor 構文リファレンス」を参照してください。
警告
コンポーネント パラメーターの初期値の指定はサポートされていますが、コンポーネントが初めてレンダリングされた後に独自のパラメーターに書き込むコンポーネントは作成しないでください。 詳細については、この記事の「上書きされたパラメーター」セクションをご覧ください。
コンポーネント パラメーターは "自動プロパティ" として宣言する必要があります。つまり、それらの get
や set
アクセサーにカスタム ロジックを含めることはできません。 たとえば、次の StartData
プロパティは自動プロパティです。
[Parameter]
public DateTime StartData { get; set; }
get
または set
アクセサーにカスタム ロジックを配置しないでください。なぜなら、コンポーネント パラメーターの目的は、親コンポーネントから子コンポーネントに情報をフローさせるためのチャネルとして使用することだけだからです。 子コンポーネントのプロパティの set
アクセサーに、親コンポーネントの再レンダリングが発生する原因となるロジックが含まれている場合、レンダリングの無限ループが発生します。
受け取ったパラメーター値を変換するには、次のようにします。
- パラメーター プロパティは、指定された生データを表す自動プロパティのままにしておきます。
- 異なるプロパティまたはメソッドを作成し、パラメーター プロパティに基づいて変換されたデータを指定します。
OnParametersSetAsync
をオーバーライドし、新しいデータを受け取るたびに受け取ったパラメーターを変換します。
コンポーネント パラメーターに初期値を書き込むことはサポートされています。これは、初期値の代入によって、Blazor の自動コンポーネント レンダリングが妨げられることはないからです。 DateTime.Now での現在のローカル DateTime の StartData
への次の代入は、コンポーネントで有効な構文です。
[Parameter]
public DateTime StartData { get; set; } = DateTime.Now;
DateTime.Now の初期代入後は、開発者コードで StartData
に値を代入しないでください。 詳細については、この記事の「上書きされたパラメーター」セクションをご覧ください。
ルート パラメーター
コンポーネントでは、@page
ディレクティブのルート テンプレートでルート パラメーターを指定できます。 Blazor ルーターでは、ルート パラメーターを使用して、対応するコンポーネント パラメーターが設定されます。
省略可能なルート パラメーターがサポートされています。 次の例では、省略可能なパラメーター text
を使用して、ルート セグメントの値をコンポーネントの Text
プロパティに割り当てます。 セグメントが存在しない場合、Text
の値は OnInitialized
ライフサイクル メソッドで "fantastic
" に設定されます。
Pages/RouteParameter.razor
:
@page "/route-parameter/{text?}"
<h1>Blazor is @Text!</h1>
@code {
[Parameter]
public string Text { get; set; }
protected override void OnInitialized()
{
Text = Text ?? "fantastic";
}
}
複数のフォルダー境界を越えるパスをキャプチャするキャッチオール ルート パラメーター ({*pageRoute}
) について詳しくは、「ASP.NET Core の Blazor ルーティングとナビゲーション」をご覧ください。
子コンテンツのレンダリング フラグメント
コンポーネントでは、別のコンポーネントのコンテンツを設定できます。 代入コンポーネントでは、子コンポーネントの開始および終了タグの間にコンテンツを指定します。
次の例では、RenderFragmentChild
コンポーネントに、RenderFragment としてレンダリングする UI のセグメントを表す ChildContent
コンポーネント パラメーターがあります。 コンポーネントの Razor マークアップ内の ChildContent
の位置は、コンテンツが最終的な HTML 出力にレンダリングされる場所です。
Shared/RenderFragmentChild.razor
:
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Child content</div>
<div class="card-body">@ChildContent</div>
</div>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
}
重要
RenderFragment コンテンツを受け取るプロパティは、規則によって ChildContent
という名前にする必要があります。
イベント コールバックは、RenderFragment ではサポートされていません。
次の RenderFragmentParent
コンポーネントでは、子コンポーネントの開始および終了タグ内にコンテンツを配置することによって RenderFragmentChild
をレンダリングするためのコンテンツを指定します。
Pages/RenderFragmentParent.razor
:
@page "/render-fragment-parent"
<h1>Render child content</h1>
<RenderFragmentChild>
Content of the child component is supplied
by the parent component.
</RenderFragmentChild>
Blazor による子コンテンツのレンダリング方法により、for
ループ内のコンポーネントのレンダリングでは、インクリメントするループ変数が RenderFragmentChild
コンポーネントのコンテンツ内で使用されている場合、ローカル インデックス変数が必要になります。 次の例は、前の RenderFragmentParent
コンポーネントに追加できます。
<h1>Three children with an index variable</h1>
@for (int c = 0; c < 3; c++)
{
var current = c;
<RenderFragmentChild>
Count: @current
</RenderFragmentChild>
}
または、for
ループではなく、Enumerable.Range と共に foreach
ループを使用します。 次の例は、前の RenderFragmentParent
コンポーネントに追加できます。
<h1>Second example of three children with an index variable</h1>
@foreach (var c in Enumerable.Range(0,3))
{
<RenderFragmentChild>
Count: @c
</RenderFragmentChild>
}
レンダリング フラグメントは、Blazor アプリ全体で子コンテンツをレンダリングするために使用され、次の記事と記事のセクションの例と共に説明されています。
注意
Blazor フレームワークの組み込み Razor コンポーネントでは、それぞれ同じ ChildContent
コンポーネント パラメーター規則を使用してコンテンツを設定します。 API ドキュメントでコンポーネント パラメーター プロパティ名ChildContent
を検索することにより、子コンテンツを設定しているコンポーネントを確認できます (検索用語 "保育" を使用して API をフィルター処理)。
再利用可能なレンダリング ロジックのフラグメントをレンダリングする
レンダリング ロジックを再利用するための方法として、子コンポーネントを純粋に取り出すことができます。 任意のコンポーネントの @code
ブロックで、RenderFragment を定義し、必要に応じて任意の場所からフラグメントをレンダリングします。
<h1>Hello, world!</h1>
@RenderWelcomeInfo
<p>Render the welcome info a second time:</p>
@RenderWelcomeInfo
@code {
private RenderFragment RenderWelcomeInfo = __builder =>
{
<p>Welcome to your new app!</p>
};
}
詳細については、レンダリング ロジックの再利用に関するページを参照してください。
上書きされたパラメーター
Blazor フレームワークでは、一般に安全な親から子へのパラメーターの割り当てが行われます。
- パラメーターが予期せずに上書きされることはありません。
- 副作用は最小限に抑えられます。 たとえば、追加のレンダリングは、無限のレンダリング ループが作成される可能性があるため、回避されます。
子コンポーネントは、親コンポーネントのレンダリング時に既存の値を上書きする可能性がある新しいパラメーター値を受け取ります。 子コンポーネントでパラメーター値が誤って上書きされることは、1 つまたは複数のデータバインド パラメーターを使用してコンポーネントを開発しており、開発者が子のパラメーターに直接書き込む場合に多く発生します。
- 子コンポーネントは、親コンポーネントの 1 つまたは複数のパラメーター値を使用してレンダリングされます。
- 子によって、パラメーターの値が直接書き込まれます。
- 親コンポーネントがレンダリングされ、子のパラメーターの値が上書きされます。
パラメーター値の上書きの可能性は、子コンポーネントのプロパティ set
アクセサーにも及びます。
重要
一般的なガイダンスとして、コンポーネントが初めてレンダリングされた後に、独自のパラメーターに直接書き込むコンポーネントを作成しないでください。
次が実行される Expander
コンポーネントについて考えてみましょう。
- 子コンテンツのレンダリング。
- コンポーネント パラメーター (
Expanded
) を使用した、子コンテンツの表示の切り替え。
次の Expander
コンポーネントで上書きされるパラメーターを示した後に、このシナリオの正しい方法を示すために変更した Expander
コンポーネントを紹介します。 説明されている動作を体験するために、次の例をローカルのサンプル アプリで使用することができます。
Shared/Expander.razor
:
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
<div class="card-body">
<h2 class="card-title">Toggle (<code>Expanded</code> = @Expanded)</h2>
@if (Expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>
@code {
[Parameter]
public bool Expanded { private get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
private void Toggle()
{
Expanded = !Expanded;
}
}
Expander
コンポーネントは、StateHasChanged を呼び出す可能性のある次の ExpanderExample
親コンポーネントに追加されます。
- 開発者コードで StateHasChanged を呼び出すと、その状態が変更されたことがコンポーネントに通知され、通常は UI を更新するためにコンポーネントの再レンダリングがトリガーされます。 StateHasChanged について詳しくは、「ASP.NET Core Razor コンポーネントのライフサイクル」と「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。
- ボタンの
@onclick
ディレクティブ属性により、イベント ハンドラーがそのボタンのonclick
イベントにアタッチされます。 イベント処理について詳しくは、「ASP.NET Core Blazor のイベント処理」をご覧ください。
Pages/ExpanderExample.razor
:
@page "/expander-example"
<Expander Expanded="true">
Expander 1 content
</Expander>
<Expander Expanded="true" />
<button @onclick="StateHasChanged">
Call StateHasChanged
</button>
初期状態では、Expanded
プロパティが切り替えられると、Expander
コンポーネントはそれぞれ独立して動作します。 子コンポーネントの状態は、想定どおりのままです。
親コンポーネント内で StateHasChanged が呼び出されると、それらのパラメーターが変更された可能性がある場合に、Blazor フレームワークは子コンポーネントを再レンダリングします。
- Blazor が明示的に確認するパラメーター型のグループに対して、パラメーターのいずれかが変更されたことを Blazor が検出した場合、子コンポーネントを再レンダリングします。
- 確認されていないパラメーター型に対しては、"パラメーターが変更されたかどうかに関係なく"、Blazor は子コンポーネントを再レンダリングします。 子コンテンツは、このカテゴリのパラメーター型に分類されます。子コンテンツは、他の変更可能なオブジェクトを参照するデリゲートである型 RenderFragment だからです。
ExpanderExample
コンポーネントの場合:
- 1 つ目の
Expander
コンポーネントで、潜在的に変更可能な RenderFragment 内の子コンテンツを設定します。そのため、親コンポーネント内で StateHasChanged を呼び出すと、自動的にコンポーネントが再レンダリングされ、Expanded
の値がその初期値であるtrue
に上書きされる可能性があります。 - 2 つ目の
Expander
コンポーネントでは、子コンテンツを設定しません。 そのため、潜在的に変更可能な RenderFragment は存在しません。 親コンポーネントで StateHasChanged を呼び出しても、子コンポーネントは自動的に再レンダリングされないので、コンポーネントのExpanded
の値は上書きされません。
前のシナリオでの状態を維持するには、Expander
コンポーネントで "プライベート フィールド" を使用して、切り替え状態を維持します。
次の変更された Expander
コンポーネント:
- 親から
Expanded
コンポーネント パラメーター値を受け入れます。 - コンポーネント パラメーター値を、
OnInitialized
イベントの "プライベート フィールド" (expanded
) に割り当てます。 - プライベート フィールドを使用して、その内部のトグル状態を維持します。これは、パラメーターに直接書き込まれないようにする方法を示しています。
Note
このセクションのアドバイスは、コンポーネント パラメーター set
アクセサーの同様のロジックに及ぶため、同様の望ましくない副作用が発生する可能性があります。
Shared/Expander.razor
:
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
<div class="card-body">
<h2 class="card-title">Toggle (<code>expanded</code> = @expanded)</h2>
@if (expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>
@code {
private bool expanded;
[Parameter]
public bool Expanded { private get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
protected override void OnInitialized()
{
expanded = Expanded;
}
private void Toggle()
{
expanded = !expanded;
}
}
詳細については、Blazor両方向のバインド エラー (dotnet/aspnetcore #24599) に関するページを参照してください。
Blazor によって確認される正確な型の情報など、変更検出の詳細については、「ASP.NET Core Razor コンポーネントのレンダリング」を参照してください。
属性スプラッティングと任意のパラメーター
コンポーネントでは、コンポーネントの宣言されたパラメーターに加えて、追加の属性をキャプチャしてレンダリングできます。 追加の属性は、ディクショナリに取り込んでから、@attributes
Razor ディレクティブ属性を使用してコンポーネントがレンダリングされるときに要素に "スプラッティング" できます。 このシナリオは、さまざまなカスタマイズをサポートするマークアップ要素を生成するコンポーネントを定義する場合に便利です。 たとえば、多くのパラメーターをサポートする <input>
に対して、属性を個別に定義するのは面倒な場合があります。
次の Splat
コンポーネントでは、以下のことを行います。
- 1 つ目の
<input>
要素 (id="useIndividualParams"
) では、個々のコンポーネント パラメーターを使用します。 - 2 つ目の
<input>
要素 (id="useAttributesDict"
) では、属性のスプラッティングを使用します。
Pages/Splat.razor
:
@page "/splat"
<input id="useIndividualParams"
maxlength="@maxlength"
placeholder="@placeholder"
required="@required"
size="@size" />
<input id="useAttributesDict"
@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" }
};
}
Web ページにレンダリングされる <input>
要素は同じです。
<input id="useIndividualParams"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">
<input id="useAttributesDict"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">
任意の属性を受け入れるには、CaptureUnmatchedValues プロパティを true
に設定したコンポーネント パラメーターを定義します。
@code {
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> InputAttributes { get; set; }
}
[Parameter]
の CaptureUnmatchedValues プロパティにより、パラメーターを他のパラメーターと一致しないすべての属性と一致させることができます。 1 つのコンポーネントで、CaptureUnmatchedValues を持つパラメーターは 1 つだけ定義できます。 CaptureUnmatchedValues で使用されるプロパティの型は、文字列キーを使用して Dictionary<string, object>
から割り当て可能である必要があります。 このシナリオでは、IEnumerable<KeyValuePair<string, object>>
または IReadOnlyDictionary<string, object>
も使用できます。
要素属性の位置を基準とした @attributes
の位置は重要です。 @attributes
が要素にスプラッティングされると、属性は右から左 (最後から最初) に処理されます。 子コンポーネントを使用する次の親コンポーネントの例を考えてみます。
Shared/AttributeOrderChild1.razor
:
<div @attributes="AdditionalAttributes" extra="5" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; }
}
Pages/AttributeOrderParent1.razor
:
@page "/attribute-order-parent-1"
<AttributeOrderChild1 extra="10" />
AttributeOrderChild1
コンポーネントの extra
属性が @attributes
の右側に設定されています。 属性は右から左 (最後から最初) に処理されるため、追加の属性によって渡された場合に、AttributeOrderParent1
コンポーネントのレンダリングされる <div>
に、extra="5"
が含まれます。
<div extra="5" />
次の例では、子コンポーネントの <div>
で extra
と @attributes
の順序が逆になります。
Shared/AttributeOrderChild2.razor
:
<div extra="5" @attributes="AdditionalAttributes" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; }
}
Pages/AttributeOrderParent2.razor
:
@page "/attribute-order-parent-2"
<AttributeOrderChild2 extra="10" />
追加の属性によって渡された場合に、親コンポーネントのレンダリングされる Web ページの <div>
には extra="10"
が含まれます。
<div extra="10" />
コンポーネントへの参照をキャプチャする
コンポーネント参照を使用すると、コマンドを発行するためのコンポーネント インスタンスを参照することができます。 コンポーネント参照をキャプチャするには:
- 子コンポーネントに
@ref
属性を追加します。 - 子コンポーネントと同じ型のフィールドを定義します。
コンポーネントがレンダリングされると、フィールドにコンポーネント インスタンスが設定されます。 その後、そのインスタンスに対して .NET メソッドを呼び出すことができます。
ChildMethod
が呼び出されたときにメッセージをログに記録する次の ReferenceChild
コンポーネントについて考えてみます。
Shared/ReferenceChild.razor
:
@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> logger
@code {
public void ChildMethod(int value)
{
logger.LogInformation("Received {Value} in ChildMethod", value);
}
}
コンポーネント参照は、コンポーネントがレンダリングされた後にのみ設定され、その出力には ReferenceChild
要素が含まれます。 コンポーネントがレンダリングされるまで、参照するものはありません。
コンポーネントのレンダリングが終了した後にコンポーネント参照を操作するには、OnAfterRender
または OnAfterRenderAsync
メソッドを使用します。
イベント ハンドラーで参照変数を使用するには、ラムダ式を使用するか、OnAfterRender
または OnAfterRenderAsync
メソッドでイベント ハンドラー デリゲートを割り当てます。 これにより、イベント ハンドラーが割り当てられる前に参照変数が確実に割り当てられます。
次のラムダ手法では、前の ReferenceChild
コンポーネントを使用します。
Pages/ReferenceParent1.razor
:
@page "/reference-parent-1"
<button @onclick="@(() => childComponent.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild childComponent;
}
次のデリゲート手法では、上記の ReferenceChild
コンポーネントを使用します。
Pages/ReferenceParent2.razor
:
@page "/reference-parent-2"
<button @onclick="callChildMethod">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild childComponent;
private Action callChildMethod;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
callChildMethod = CallChildMethod;
}
}
private void CallChildMethod()
{
childComponent.ChildMethod(5);
}
}
コンポーネント参照の取り込みでは、要素参照の取り込みと同様の構文を使用しますが、コンポーネント参照の取り込みは JavaScript 相互運用機能ではありません。 コンポーネント参照は、JavaScript コードに渡されません。 コンポーネント参照は、.NET コードでのみ使用されます。
重要
子コンポーネントの状態を変えるためにコンポーネント参照を使用しないでください。 代わりに、通常の宣言型コンポーネント パラメーターを使用して、子コンポーネントにデータを渡します。 コンポーネント パラメーターを使用すると、子コンポーネントが正しいタイミングで自動的にレンダリングされます。 詳しくは、「コンポーネントのパラメーター」セクションと、記事「ASP.NET Core Blazor データ バインディング」をご覧ください。
同期コンテキスト
Blazor では、同期コンテキスト (SynchronizationContext) を使用して、1 つの実行の論理スレッドを強制します。 コンポーネントのライフサイクル メソッドと、Blazor によって発生するイベント コールバックは、同期コンテキストで実行されます。
Blazor Server の同期コンテキストでは、ブラウザーの WebAssembly モデル (シングル スレッド) と厳密に一致するように、シングルスレッド環境のエミュレートが試行されます。 どの時点でも、作業は 1 つのスレッドでのみ実行され、1 つの論理スレッドであるという印象になります。 2 つの操作が同時に実行されることはありません。
スレッドをブロックする呼び出しを避ける
一般に、コンポーネントでは次のメソッドは呼び出さないでください。 次のメソッドでは実行スレッドがブロックされます。そのため、基になる Task が完了するまで、アプリの動作が再開されなくなります。
Note
このセクションに示されているスレッド ブロック メソッドを使用する Blazor ドキュメントの例では、推奨されるコーディング ガイダンスとしてではなく、デモンストレーション目的でのみメソッドを使用しています。 たとえば、いくつかのコンポーネント コードのデモでは、Thread.Sleep を呼び出して、実行時間の長いプロセスをシミュレートします。
状態を更新するために外部でコンポーネント メソッドを呼び出す
タイマーやその他の通知などの外部イベントに基づいてコンポーネントを更新する必要がある場合は、InvokeAsync
メソッドを使用します。これにより、Blazor の同期コンテキストにコードの実行がディスパッチされます。 たとえば、リッスンしているコンポーネントに、更新状態について通知できる次の ''通知サービス'' を考えてみます。 Update
メソッドは、アプリ内のどこからでも呼び出すことができます。
TimerService.cs
:
using System;
using System.Timers;
using Microsoft.Extensions.Logging;
public class TimerService : IDisposable
{
private int elapsedCount;
private readonly ILogger<TimerService> logger;
private readonly NotifierService notifier;
private Timer timer;
public TimerService(NotifierService notifier, ILogger<TimerService> logger)
{
this.notifier = notifier;
this.logger = logger;
}
public void Start()
{
if (timer is null)
{
timer = new();
timer.AutoReset = true;
timer.Interval = 10000;
timer.Elapsed += HandleTimer;
timer.Enabled = true;
logger.LogInformation("Started");
}
}
private async void HandleTimer(object source, ElapsedEventArgs e)
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation($"elapsedCount: {elapsedCount}");
}
public void Dispose()
{
timer?.Dispose();
}
}
NotifierService.cs
:
using System;
using System.Threading.Tasks;
public class NotifierService
{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}
public event Func<string, int, Task> Notify;
}
サービスを登録します。
Blazor WebAssembly アプリでは、
Program.cs
のシングルトンとしてサービスを登録します。builder.Services.AddSingleton<NotifierService>(); builder.Services.AddSingleton<TimerService>();
Blazor Server アプリでは、
Startup.ConfigureServices
を範囲としてサービスを登録します。services.AddScoped<NotifierService>(); services.AddScoped<TimerService>();
NotifierService
を使用して、コンポーネントを更新します。
Pages/ReceiveNotifications.razor
:
@page "/receive-notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer
<h1>Receive Notifications</h1>
<h2>Timer Service</h2>
<button @onclick="StartTimer">Start Timer</button>
<h2>Notifications</h2>
<p>
Status:
@if (lastNotification.key is not null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting first notification</span>
}
</p>
@code {
private (string key, int value) lastNotification;
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public async Task OnNotify(string key, int value)
{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}
private void StartTimer()
{
Timer.Start();
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
前の例の場合:
- Blazor の同期コンテキスト外で
NotifierService
からコンポーネントのOnNotify
メソッドが呼び出されます。InvokeAsync
を使用して、正しいコンテキストに切り替え、レンダリングをキューに登録します。 詳しくは、「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。 - コンポーネントでは IDisposable を実装します。
OnNotify
デリゲートのサブスクライブはDispose
メソッドで解除されます。このメソッドは、コンポーネントが破棄されたときにフレームワークによって呼び出されます。 詳しくは、「ASP.NET Core Razor コンポーネントのライフサイクル」をご覧ください。
要素またはコンポーネントの保持の制御での @key
の使用
要素またはコンポーネントのリストをレンダリングし、その後に要素またはコンポーネントが変更された場合、Blazor では、前のどの要素やコンポーネントを保持できるか、およびモデル オブジェクトをそれらにどのようにマップするかを決定する必要があります。 通常、このプロセスは自動で、無視できますが、プロセスの制御が必要になる場合があります。
次の Details
および People
コンポーネントについて考えてみます。
Details
コンポーネントでは、<input>
要素に表示される親のPeople
コンポーネントからデータ (Data
) を受け取ります。 表示される任意の<input>
要素では、<input>
要素のいずれかを選択すると、ユーザーからページのフォーカスを受け取ることができます。People
コンポーネントでは、Details
コンポーネントを使用して表示する person オブジェクトのリストを作成します。 3 秒ごとに、新しい個人がコレクションに追加されます。
このデモで次のことを行うことができます。
- レンダリングされた複数の
Details
コンポーネントの中から<input>
を選択する。 - people コレクションの自動拡大時のページのフォーカスの動作を調べる。
Shared/Details.razor
:
<input value="@Data" />
@code {
[Parameter]
public string Data { get; set; }
}
次の People
コンポーネントでは、個人を OnTimerCallback
に追加する各イテレーションによって、Blazor でコレクション全体がリビルドされます。 ページのフォーカスは、<input>
要素の ''同じインデックス'' 位置に留まります。そのため、個人が追加されるたびにフォーカスが移動します。 ''ユーザーが選択した内容からフォーカスを移動することは、望ましい動作ではありません。 '' 次のコンポーネントでの不適切な動作を示した後、ユーザーのエクスペリエンスを向上させるために @key
ディレクティブ属性が使用されます。
Pages/People.razor
:
@page "/people"
@using System.Timers
@implements IDisposable
@foreach (var person in people)
{
<Details Data="@person.Data" />
}
@code {
private Timer timer = new Timer(3000);
public List<Person> people =
new()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
});
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
public class Person
{
public string Data { get; set; }
}
}
people
コレクションのコンテンツは、挿入、削除、または順序変更されたエントリによって変更されます。 再レンダリングによって、表示動作に違いが生じる可能性があります。 people
コレクションに個人が挿入されるたびに、現在フォーカスがある要素の ''前の要素'' でフォーカスを受け取ります。 ユーザーのフォーカスは失われます。
要素またはコンポーネントのコレクションへのマッピング プロセスは、@key
ディレクティブ属性を使用して制御できます。 @key
を使用すると、キーの値に基づいて要素またはコンポーネントが確実に保持されます。 前の例の Details
コンポーネントが person
項目にキー指定されている場合、Blazor では、変更されていない Details
コンポーネントのレンダリングが無視されます。
people
コレクションで @key
ディレクティブ属性を使用するように People
コンポーネントを変更するには、<Details>
要素を次のように更新します。
<Details @key="person" Data="@person.Data" />
people
コレクションが変更されても、Details
インスタンスと person
インスタンス間の関連付けは保持されます。 コレクションの先頭に Person
が挿入されると、その対応する位置に 1 つの新しい Details
インスタンスが挿入されます。 他のインスタンスは変更されません。 そのため、コレクションに人々が追加されても、ユーザーのフォーカスは失われません。
@key
ディレクティブ属性が使用されている場合、他のコレクションの更新でも同じ動作になります。
- コレクションからインスタンスが削除された場合、対応するコンポーネント インスタンスのみが UI から除去されます。 他のインスタンスは変更されません。
- コレクション エントリの順序が変更された場合、対応するコンポーネント インスタンスは UI で保持され、順序が変更されます。
重要
キーは、各コンテナー要素やコンポーネントに対してローカルです。 キーはドキュメント全体でグローバルに比較されません。
どのようなときに @key
を使用するか
一般に、リストがレンダリングされ (たとえば、foreach
ブロックで)、@key
を定義するための適切な値が存在する場合は常に、@key
を使用することは意味があります。
次の例に示すように、@key
を使用して、オブジェクトが変更されない場合に要素またはコンポーネントのサブツリーを保持することもできます。
例 1:
<li @key="person">
<input value="@person.Data" />
</li>
例 2:
<div @key="person">
@* other HTML elements *@
</div>
person
インスタンスが変更された場合、@key
属性ディレクティブにより、Blazor に次のことが強制されます。
<li>
または<div>
の全体およびその子孫を破棄する。- 新しい要素とコンポーネントを使用して、UI 内でサブツリーをリビルドする。
これは、コレクションがサブツリー内で変更されたときに UI の状態が確実に保持されないようにするのに役立ちます。
@key
のスコープ
@key
属性のディレクティブのスコープは、その親内の自身の兄弟です。
次の例を考えてみます。 first
および second
キーは、外側の <div>
要素内の同じスコープで互いに比較されます。
<div>
<div @key="first">...</div>
<div @key="second">...</div>
</div>
次の例では、互いに関係がなく、互いに影響を与えることのない、独自のスコープの first
と second
キーを示しています。 各 @key
のスコープはその親 <div>
要素のみであり、各親 <div>
要素ではありません。
<div>
<div @key="first">...</div>
</div>
<div>
<div @key="second">...</div>
</div>
前に示した Details
コンポーネントの場合、次の例では、同じ @key
のスコープ内の person
データがレンダリングされます。これが @key
の一般的な使用例です。
<div>
@foreach (var person in people)
{
<Details @key="person" Data="@person.Data" />
}
</div>
@foreach (var person in people)
{
<div @key="person">
<Details Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li @key="person">
<Details Data="@person.Data" />
</li>
}
</ol>
次の例の @key
のスコープは、各 Details
コンポーネント インスタンスを囲む、<div>
または <li>
要素のみです。 つまり、people
コレクションの各メンバーの person
データは、レンダリングされた各 Details
コンポーネントの各 person
インスタンスのキーではありません。 @key
を使用する場合は、次のパターンを避けてください。
@foreach (var person in people)
{
<div>
<Details @key="person" Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li>
<Details @key="person" Data="@person.Data" />
</li>
}
</ol>
どのようなときに @key
を使用しないか
@key
でレンダリングすると、パフォーマンスが低下します。 パフォーマンスの低下は大きくありませんが、要素やコンポーネントを保持することによって、アプリにメリットがある場合にのみ @key
を指定してください。
@key
を使用しない場合でも、Blazor では可能な限り、子要素とコンポーネント インスタンスが保持されます。 @key
を使用する唯一の利点は、マッピングを選択する Blazor ではなく、保持されているコンポーネント インスタンスにモデル インスタンスをマップする "方法" が制御されることです。
@key
に使用する値
一般に、@key
には、次のいずれかの値を指定するのが適切です。
- モデル オブジェクト インスタンス。 たとえば、前の例では
Person
インスタンス (person
) が使用されていました。 これにより、オブジェクト参照の等価性に基づいて保持されます。 - 一意識別子。 たとえば、一意識別子は
int
、string
、またはGuid
型の主キー値を基にすることができます。
@key
に使用される値は競合しないようにしてください。 同じ親要素内で競合する値が検出された場合、Blazor では、古い要素やコンポーネントを新しい要素やコンポーネントに確定的にマップできないため、例外がスローされます。 個別の値 (オブジェクト インスタンスや主キー値など) のみを使用してください。
属性を適用する
属性は、@attribute
ディレクティブを使用してコンポーネントに適用できます。 次の例では、[Authorize]
属性をコンポーネントのクラスに適用しています。
@page "/"
@attribute [Authorize]
条件付き HTML 要素属性
HTML 要素属性プロパティは、.NET 値に基づいて条件付きで設定されます。 値が false
または null
の場合、プロパティは設定されません。 値が true
の場合は、プロパティが設定されます。
次の例では、IsCompleted
により、<input>
要素の checked
プロパティが設定されるかどうかが決定されます。
Pages/ConditionalAttribute.razor
:
@page "/conditional-attribute"
<label>
<input type="checkbox" checked="@IsCompleted" />
Is Completed?
</label>
<button @onclick="@(() => IsCompleted = !IsCompleted)">
Change IsCompleted
</button>
@code {
[Parameter]
public bool IsCompleted { get; set; }
}
詳細については、「ASP.NET Coreの Razor 構文リファレンス」を参照してください。
警告
.NET 型が bool
の場合、aria-pressed
などの一部の HTML 属性が正しく機能しません。 そのような場合は、bool
ではなく string
型を使用します。
生 HTML
通常、文字列は DOM テキスト ノードを使用してレンダリングされます。つまり、それらに含まれている可能性のあるすべてのマークアップが無視され、リテラル テキストとして扱われます。 生 HTML をレンダリングするには、HTML コンテンツを MarkupString 値にラップします。 値は HTML または SVG として解析され、DOM に挿入されます。
警告
信頼されていないソースから構築された生 HTML をレンダリングすることは、セキュリティ リスクであるため、常に避ける必要があります。
次の例では、MarkupString 型を使用して、コンポーネントのレンダリングされた出力に静的 HTML コンテンツのブロックを追加しています。
Pages/MarkupStringExample.razor
:
@page "/markup-string-example"
@((MarkupString)myMarkup)
@code {
private string myMarkup =
"<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
}
Razor テンプレート
レンダリング フラグメントは、UI スニペットを定義するための Razor テンプレート構文を使用して定義できます。 Razor テンプレートでは次の形式を使用します。
@<{HTML tag}>...</{HTML tag}>
次の例では、RenderFragment と RenderFragment<TValue> の値を指定し、コンポーネント内にテンプレートを直接レンダリングする方法を示しています。 レンダリング フラグメントは、引数としてテンプレート コンポーネントに渡すこともできます。
Pages/RazorTemplate.razor
:
@page "/razor-template"
@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; }
}
}
前のコードのレンダリングされた結果:
<p>The time is 4/19/2021 8:54:46 AM.</p>
<p>Pet: Nutty Rex</p>
静的な資産
静的資産の場合、Blazor では ASP.NET Core アプリの規則に従います。 静的資産は、プロジェクトの web root
(wwwroot
) フォルダー、または wwwroot
フォルダーの下のフォルダーにあります。
静的アセットの Web ルートを参照するには、ベース相対パス (/
) を使用します。 次の例では、logo.png
が物理的に {PROJECT ROOT}/wwwroot/images
フォルダーに配置されています。 {PROJECT ROOT}
は、アプリのプロジェクト ルートです。
<img alt="Company logo" src="/images/logo.png" />
コンポーネントでは、チルダ スラッシュ表記 (~/
) はサポートされていません。
アプリのベース パスの設定について詳しくは、「ASP.NET Core Blazor のホストと展開」をご覧ください。
タグ ヘルパーはコンポーネントでサポートされない
Tag Helpers
はコンポーネントでサポートされていません。 Blazor にタグ ヘルパーのような機能を提供するには、タグ ヘルパーと同じ機能を持つコンポーネントを作成し、代わりにそのコンポーネントを使用します。
スケーラブル ベクター グラフィックス (SVG) イメージ
Blazor は HTML をレンダリングするため、スケーラブル ベクター グラフィックス (SVG) 画像 (.svg
) などのブラウザーでサポートされている画像は、<img>
タグを介してサポートされます。
<img alt="Example image" src="image.svg" />
同様に、SVG 画像は、スタイルシート ファイル (.css
) の CSS 規則でサポートされています。
.element-class {
background-image: url("image.svg");
}
空白文字のレンダリング動作
@preservewhitespace
ディレクティブが true
の値と共に使用されている場合を除き、既定では、次の場合に余分な空白が削除されます。
- 要素内の先頭または末尾。
- RenderFragment/RenderFragment<TValue> パラメーター内の先頭または末尾 (たとえば、別のコンポーネントに渡された子コンテンツ)。
@if
または@foreach
のような、C# コード ブロックの前か後にある。
空白文字の削除は、white-space: pre
などの CSS ルールを使用するときに、レンダリングされた出力に影響を与えることがあります。 このパフォーマンスの最適化を無効にして、空白を保持するには、次のいずれかの操作を実行します。
- Razor ファイル (
.razor
) の先頭に@preservewhitespace true
ディレクティブを追加し、特定のコンポーネントに設定を適用する。 _Imports.razor
ファイル内に@preservewhitespace true
ディレクティブを追加し、サブディレクトリまたはプロジェクト全体に設定を適用する。
ほとんどの場合、アプリでは一般的に通常の動作が続行されるため (ただし、速くなります)、何の措置も必要ありません。 空白文字を削除すると特定のコンポーネントでレンダリングの問題が発生する場合は、そのコンポーネントで @preservewhitespace true
を使用し、この最適化を無効にします。
ジェネリック型パラメーターのサポート
@typeparam
ディレクティブによって、生成されるコンポーネント クラスのジェネリック型パラメーターを宣言します。
@typeparam TItem
次の例では、ListGenericTypeItems1
コンポーネントは TExample
としてジェネリック型に指定されます。
Shared/ListGenericTypeItems1.razor
:
@typeparam TExample
@if (ExampleList is not null)
{
<ul>
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}
@code {
[Parameter]
public IEnumerable<TExample> ExampleList{ get; set; }
}
次の GenericTypeExample1
コンポーネントでは、2 つの ListGenericTypeItems1
コンポーネントがレンダリングされます。
- 文字列または整数のデータは、各コンポーネントの
ExampleList
パラメーターに割り当てられます。 - 割り当てられたデータの型に一致する型
string
またはint
が、各コンポーネントの型パラメーター (TExample
) に設定されます。
Pages/GenericTypeExample1.razor
:
@page "/generic-type-example-1"
<h1>Generic Type Example 1</h1>
<ListGenericTypeItems1 ExampleList="@(new List<string> { "Item 1", "Item 2" })"
TExample="string" />
<ListGenericTypeItems1 ExampleList="@(new List<int> { 1, 2, 3 })"
TExample="int" />
詳細については、次の記事を参照してください。
Blazor アプリは、"Razor コンポーネント" (非公式には "Blazor コンポーネント" と呼ばれます) を使って構築されます。 コンポーネントは、動的な動作を有効にするための処理ロジックを使用するユーザー インターフェイス (UI) の自己完結型の部分です。 コンポーネントは、入れ子にしたり、再利用したり、プロジェクト間で共有したり、MVC および Razor ページ アプリで使用したりすることができます。
コンポーネント クラス
コンポーネントは、.razor
ファイル拡張子の Razor コンポーネント ファイルで C# と HTML マークアップの組み合わせを使用して実装されます。
Razor の構文
コンポーネントでは Razor 構文を使用します。 コンポーネント、''ディレクティブ'' および ''ディレクティブ属性'' では、2 つの Razor 機能が広く使用されています。 これらは、Razor マークアップに表示される @
というプレフィックスが付いた予約キーワードです。
- ディレクティブ: コンポーネント マークアップの解析方法または関数を変更します。 たとえば、
@page
ディレクティブでは、ルート テンプレートでルーティング可能なコンポーネントを指定します。このディレクティブには、ブラウザーの特定の URL でのユーザーの要求により直接到達できます。 - ディレクティブ属性: コンポーネント要素の解析方法または関数を変更します。 たとえば、
<input>
要素の@bind
ディレクティブ属性では、データをその要素の値にバインドします。
コンポーネントで使用されるディレクティブとディレクティブ属性については、この記事および Blazor ドキュメント セットの他の記事で詳しく説明されています。 Razor の構文の一般的な情報については、「ASP.NET Coreの Razor 構文リファレンス」をご覧ください。
名前
コンポーネントの名前は大文字で始める必要があります。
ProductDetail.razor
は有効です。productDetail.razor
が無効です。
Blazor ドキュメント全体で使用される共通の Blazor の名前付け規則は次のとおりです。
- コンポーネント ファイルのパスでは、パスカル ケース†が使用され、コンポーネントのコード例が示される前に表示されます。 パスは一般的なフォルダーの場所を示します。 たとえば、
Pages/ProductDetail.razor
は、ProductDetail
コンポーネントのファイル名がProductDetail.razor
で、そのコンポーネントがアプリのPages
フォルダーに存在することを示します。 - ルーティング可能なコンポーネントのコンポーネント ファイル パスは、コンポーネントのルート テンプレート内の単語間のスペースに表示されるハイフン付きの URL に一致します。 たとえば、ルート テンプレートが
/product-detail
(@page "/product-detail"
) のProductDetail
コンポーネントは、ブラウザーの相対 URL/product-detail
で要求されます。
† パスカル ケース (大文字のキャメル ケース) は、スペースと句読点を使用せず、大文字の各単語の最初の文字 (最初の単語を含む) を使用する名前付け規則です。
ルーティング
Blazor でのルーティングは、@page
ディレクティブを使用するアプリ内のアクセス可能な各コンポーネントへのルート テンプレートを提供することで実現します。 @page
ディレクティブを含む Razor ファイルがコンパイルされると、生成されたクラスに、ルート テンプレートを指定する RouteAttribute が指定されます。 実行時に、ルーターによって RouteAttribute を持つコンポーネント クラスが検索され、要求された URL に一致するルート テンプレートを使用するコンポーネントがレンダリングされます。
次の HelloWorld
コンポーネントでは、/hello-world
のルート テンプレートを使用します。 コンポーネントのレンダリングされた Web ページには、相対 URL /hello-world
で到達できます。 既定のプロトコル、ホスト、およびポートを使用して Blazor アプリをローカルで実行する場合、HelloWorld
コンポーネントはブラウザーの https://localhost:5001/hello-world
で要求されます。 Web ページを生成するコンポーネントは通常、Pages
フォルダーに存在しますが、入れ子になったフォルダー内などのコンポーネントを保持するために任意のフォルダーを使用できます。
Pages/HelloWorld.razor
:
@page "/hello-world"
<h1>Hello World!</h1>
コンポーネントをアプリの UI ナビゲーションに追加するかどうかに関係なく、前のコンポーネントはブラウザーの /hello-world
で読み込まれます。 必要に応じて、コンポーネントを NavMenu
コンポーネントに追加し、そのコンポーネントへのリンクがアプリの UI ベースのナビゲーションに表示されるようにすることができます。
前の HelloWorld
コンポーネントでは、NavLink
コンポーネントを Shared
フォルダーの NavMenu
コンポーネントに追加できます。 NavLink
や NavMenu
コンポーネントの説明など詳しくは、「ASP.NET Core の Blazor ルーティングとナビゲーション」をご覧ください。
マークアップ
コンポーネントの UI は、Razor マークアップ、C#、HTML で構成される Razor 構文を使用して定義されます。 アプリがコンパイルされると、HTML マークアップと C# のレンダリング ロジックはコンポーネント クラスに変換されます。 生成されたクラスの名前は、ファイルの名前と一致します。
コンポーネント クラスのメンバーは、1 つまたは複数の @code
ブロック内で定義されます。 @code
ブロックでは、コンポーネントの状態が指定され、C# で処理されます。
- プロパティとフィールドの初期化子。
- 親コンポーネントとルート パラメーターによって渡される引数からのパラメーター値。
- ユーザー イベント処理、ライフサイクル イベント、およびカスタム コンポーネント ロジックのメソッド。
コンポーネント メンバーは、@
記号で始まる C# 式を使ったレンダリング ロジックで使用されます。 たとえば、フィールド名の前に @
を付けることによって、C# フィールドがレンダリングされます。 次の Markup
コンポーネントでは、以下を評価およびレンダリングします。
- 見出し要素の CSS プロパティ値
headingFontStyle
のfont-style
。 - 見出し要素のコンテンツの
headingText
。
Pages/Markup.razor
:
@page "/markup"
<h1 style="font-style:@headingFontStyle">@headingText</h1>
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
}
注意
Blazor ドキュメント全体の例では、プライベート メンバーの private
アクセス修飾子を指定します。 プライベート メンバーは、コンポーネントのクラスにスコープ指定されます。 しかし、C# では、アクセス修飾子が存在しない場合、private
アクセス修飾子が想定されるため、独自のコードでメンバーを "private
" として明示的にマークすることは省略可能です。 アクセス修飾子の詳細については、「アクセス修飾子 (C# プログラミング ガイド)」を参照してください。
Blazor フレームワークでは、コンポーネントを内部的にレンダリング ツリーとして処理します。これは、コンポーネントのドキュメント オブジェクト モデル (DOM) とカスケード スタイル シートオブジェクト モデル (CSSOM) の組み合わせです。 コンポーネントが最初にレンダリングされた後に、そのコンポーネントのレンダリング ツリーがイベントに応じて再生成されます。 Blazor によって新旧のレンダリング ツリーが比較され、表示目的でブラウザーの DOM に変更がすべて適用されます。 詳しくは、「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。
コンポーネントは通常の C# クラスであり、プロジェクト内の任意の場所に配置できます。 Web ページを生成するコンポーネントは、通常、Pages
フォルダーに存在します。 ページ以外のコンポーネントは、多くの場合、Shared
フォルダー、またはプロジェクトに追加されたカスタム フォルダーに配置されます。
C# コントロール構造体、ディレクティブ、およびディレクティブ属性の Razor 構文は小文字です (例: @if
、@code
、@bind
)。 プロパティ名は大文字です (例: LayoutComponentBase.Body の @Body
)。
非同期メソッド (async
) では、void
を返すことはサポートされていません
Blazor フレームワークでは、void
を返す非同期メソッド (async
) は追跡されません。 その結果、void
が返された場合、例外はキャッチされません。 非同期メソッドからは、常に Task が返されます。
入れ子になったコンポーネント
コンポーネントには、HTML 構文を使用して宣言することで、他のコンポーネントを含めることができます。 コンポーネントを使うためのマークアップは、そのコンポーネントの種類をタグ名とする HTML タグのようになります。
他のコンポーネントで見出しを表示するために使用できる次の Heading
コンポーネントについて考えてみます。
Shared/Heading.razor
:
<h1 style="font-style:@headingFontStyle">Heading Example</h1>
@code {
private string headingFontStyle = "italic";
}
HeadingExample
コンポーネント内の次のマークアップでは、<Heading />
タグが表示される位置に、前の Heading
コンポーネントをレンダリングします。
Pages/HeadingExample.razor
:
@page "/heading-example"
<Heading />
同じ名前空間内のコンポーネント名と一致しない最初の文字が大文字の HTML 要素がコンポーネントに含まれている場合、要素に予期しない名前が付いていることを示す警告が出力されます。 コンポーネントの名前空間に @using
ディレクティブを追加すると、コンポーネントを使用できるようになり、警告が解決されます。 詳細については、「名前空間」セクションを参照してください。
このセクションに示されている Heading
コンポーネントの例には @page
ディレクティブがないため、Heading
コンポーネントには、ブラウザーで直接要求を使用してユーザーが直接アクセスすることはできません。 しかし、@page
ディレクティブを持つコンポーネントは、他のコンポーネントの入れ子にすることができます。 Razor ファイルの先頭に @page "/heading"
を含めることによって Heading
コンポーネントに直接アクセスできた場合は、/heading
と /heading-example
の両方でブラウザー要求に対してコンポーネントがレンダリングされます。
名前空間
一般に、コンポーネントの名前空間は、アプリのルート名前空間と、アプリ内のコンポーネントの場所 (フォルダー) から派生します。 アプリのルート名前空間が BlazorSample
で、Counter
コンポーネントが Pages
フォルダーに存在する場合:
Counter
コンポーネントの名前空間はBlazorSample.Pages
になります。- コンポーネントの完全修飾型名は
BlazorSample.Pages.Counter
になります。
コンポーネントを保持するカスタム フォルダーの場合は、@using
ディレクティブを親コンポーネントまたはアプリの _Imports.razor
ファイルに追加します。 次の例では、Components
フォルダー内のコンポーネントを使用できるようにします。
@using BlazorSample.Components
Note
_Imports.razor
ファイルの @using
ディレクティブは、C# ファイル (.cs
) ではなく Razor ファイル (.razor
) にのみ適用されます。
コンポーネントは、完全修飾名を使用して参照することもできます。この場合、@using
ディレクティブは必要ありません。 次の例では、アプリの Components
フォルダーで ProductDetail
コンポーネントを直接参照します。
<BlazorSample.Components.ProductDetail />
Razor で作成されるコンポーネントの名前空間は、(優先順に並んだ) 以下に基づきます。
- Razor ファイルのマークアップ内の
@namespace
ディレクティブ (@namespace BlazorSample.CustomNamespace
など)。 - プロジェクト ファイル内のプロジェクトの
RootNamespace
(<RootNamespace>BlazorSample</RootNamespace>
など)。 - プロジェクト ファイルのファイル名 (
.csproj
) から取得されたプロジェクト名、およびプロジェクト ルートからコンポーネントへのパス。 たとえば、フレームワークでは、プロジェクト名前空間がBlazorSample
(BlazorSample.csproj
) の{PROJECT ROOT}/Pages/Index.razor
は、Index
コンポーネントの名前空間BlazorSample.Pages
に解決されます。{PROJECT ROOT}
はプロジェクトのルート パスです。 コンポーネントは C# の名前のバインド規則に従います。 この例のIndex
コンポーネントの場合、スコープ内のコンポーネントは、次のすべてのコンポーネントです。- 同じフォルダー (
Pages
) に含まれるもの。 - 別の名前空間を明示的に指定しない、プロジェクトのルート内のコンポーネント。
- 同じフォルダー (
以下はサポートされていません。
global::
修飾。- 別名が付けられた
using
ステートメントによるコンポーネントのインポート。 たとえば、@using Foo = Bar
はサポートされていません。 - 部分修飾名。 たとえば、
@using BlazorSample
をコンポーネントに追加してから、アプリのShared
フォルダー (Shared/NavMenu.razor
) のNavMenu
コンポーネントを<Shared.NavMenu></Shared.NavMenu>
で参照することはできません。
部分クラスのサポート
コンポーネントは C# 部分クラスとして生成され、次のいずれかの方法を使用して作成されます。
- 1 つのファイルには、1 つまたは複数の
@code
ブロックで定義されている C# コード、HTML マークアップ、および Razor マークアップが含まれています。 Blazor プロジェクト テンプレートでは、この 1 つのファイルの方法を使用してコンポーネントを定義します。 - HTML および Razor マークアップは、Razor ファイル (
.razor
) に配置されます。 C# コードは、部分クラスとして定義されている分離コード ファイル (.cs
) に配置されます。
次の例は、Blazor プロジェクト テンプレートから生成されたアプリ内の @code
ブロックを含む既定の Counter
コンポーネントを示しています。 マークアップと C# コードは同じファイル内にあります。 これは、コンポーネントの作成で最も一般的に使用される方法です。
Pages/Counter.razor
:
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
次の Counter
コンポーネントでは、部分クラスで分離コード ファイルを使用して、C# コードから HTML と Razor マークアップを分割します。
Pages/CounterPartialClass.razor
:
@page "/counter-partial-class"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
Pages/CounterPartialClass.razor.cs
:
namespace BlazorSample.Pages
{
public partial class CounterPartialClass
{
private int currentCount = 0;
void IncrementCount()
{
currentCount++;
}
}
}
_Imports.razor
ファイルの @using
ディレクティブは、C# ファイル (.cs
) ではなく Razor ファイル (.razor
) にのみ適用されます。 必要に応じて、部分クラス ファイルに名前空間を追加します。
コンポーネントで使用される一般的な名前空間は次のとおりです。
using System.Net.Http;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
一般的な名前空間には、アプリの名前空間と、そのアプリの Shared
フォルダーに対応する名前空間も含まれます。
using BlazorSample;
using BlazorSample.Shared;
基本クラスの指定
@inherits
ディレクティブは、コンポーネントの基本クラスを指定するために使用されます。 次の例は、コンポーネントで基本クラスを継承し、コンポーネントのプロパティとメソッドを提供する方法を示しています。 BlazorRocksBase
基本クラスは、ComponentBase から派生します。
Pages/BlazorRocks.razor
:
@page "/blazor-rocks"
@inherits BlazorRocksBase
<h1>@BlazorRocksText</h1>
BlazorRocksBase.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample
{
public class BlazorRocksBase : ComponentBase
{
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
}
}
コンポーネントのパラメーター
''コンポーネント パラメーター'' によりデータがコンポーネントに渡されます。これらのパラメーターは、[Parameter]
属性を指定したコンポーネント クラス上で、パブリック C# プロパティを使用して定義されます。 次の例では、組み込みの参照型 (System.String) とユーザー定義の参照型 (PanelBody
) がコンポーネント パラメーターとして渡されます。
PanelBody.cs
:
public class PanelBody
{
public string Text { get; set; }
public string Style { get; set; }
}
Shared/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 PanelBody()
{
Text = "Set by child.",
Style = "normal"
};
}
警告
コンポーネント パラメーターの初期値の指定はサポートされていますが、コンポーネントが初めてレンダリングされた後に独自のパラメーターに書き込むコンポーネントは作成しないでください。 詳細については、この記事の「上書きされたパラメーター」セクションをご覧ください。
ParameterChild
コンポーネントの Title
および Body
コンポーネント パラメーターは、コンポーネントのインスタンスをレンダリングする HTML タグの引数によって設定されます。 次の ParameterParent
コンポーネントでは、2 つの ParameterChild
コンポーネントがレンダリングされます。
- 1 つ目の
ParameterChild
コンポーネントは、パラメーター引数を指定せずにレンダリングされます。 - 2 つ目の
ParameterChild
コンポーネントでは、明示的な C# 式を使用してPanelBody
のプロパティの値を設定するParameterParent
コンポーネントからのTitle
とBody
の値を受け取ります。
Pages/ParameterParent.razor
:
@page "/parameter-parent"
<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" })" />
ParameterParent
コンポーネントでコンポーネントのパラメーター値が指定されていない場合、ParameterParent
コンポーネントの次のレンダリングされた HTML マークアップに ParameterChild
コンポーネントの既定値が表示されます。 ParameterParent
コンポーネントでコンポーネントのパラメーター値が指定されている場合は、その ParameterChild
コンポーネントの既定値が置き換えられます。
Note
わかりやすくするために、レンダリングされた CSS スタイル クラスは次のレンダリングされた HTML マークアップには表示されていません。
<h1>Child component (without attribute values)</h1>
<div>
<div>Set By Child</div>
<div>Set by child.</div>
</div>
<h1>Child component (with attribute values)</h1>
<div>
<div>Set by Parent</div>
<div>Set by parent.</div>
</div>
Razor の予約済みの @
記号を使用して、メソッドの C# フィールド、プロパティ、または結果を HTML 属性値としてコンポーネント パラメーターに代入します。 次の ParameterParent2
コンポーネントでは、上記の ParameterChild
コンポーネントの 4 つのインスタンスが表示され、それらの Title
パラメーター値が次のように設定されます。
title
フィールドの値。GetTitle
C# メソッドの結果。- 暗黙的な C# 式を使用する ToLongDateString による長い形式での現在のローカル日付。
panelData
オブジェクトのTitle
プロパティ。
文字列パラメーターには @
プレフィックスが必要です。 そうしないと、フレームワークでは文字列リテラルが設定されているものと想定されます。
文字列パラメーターの外部では、厳密には必要ない場合でも、非リテラルに @
プレフィックスを使うことをお勧めします。
リテラル (ブール値など)、キーワード (this
など)、または null
には @
プレフィックスを使うことをお勧めしませんが、必要であれば使ってもかまいません。 たとえば、IsFixed="@true"
は一般的ではありませんがサポートされています。
HTML5 の仕様によると、ほとんどの場合、パラメーター属性値を囲む引用符は省略可能です。 たとえば、Value="this"
の代わりに Value=this
を使ってもかまいません。 ただし、覚えやすく、Web ベースのテクノロジで広く採用されているため、引用符を使うことをお勧めします。
このドキュメントのコード例では次にようになっています。
- 常に引用符を使用します。 例:
Value="this"
. - 非リテラルでは、省略可能な場合でも、常に
@
プレフィックスを使います。 例:Title="@title"
:title
は文字列型の変数です。Count="@ct"
:ct
は数値型の変数です。 - Razor 式の外部のリテラルでは、常に
@
を使いません。 例:IsFixed="true"
.
Pages/ParameterParent2.razor
:
@page "/parameter-parent-2"
<ParameterChild Title="@title" />
<ParameterChild Title="@GetTitle()" />
<ParameterChild Title="@DateTime.Now.ToLongDateString()" />
<ParameterChild Title="@panelData.Title" />
@code {
private string title = "From Parent field";
private PanelData panelData = new PanelData();
private string GetTitle()
{
return "From Parent method";
}
private class PanelData
{
public string Title { get; set; } = "From Parent object";
}
}
注意
C# メンバーをコンポーネント パラメーターに代入する場合は、メンバーにプレフィックスとして @
記号を付け、パラメーターの HTML 属性にはプレフィックスは付けません。
正しい:
<ParameterChild Title="@title" />
正しくない:
<ParameterChild @Title="title" />
Razor ページ (.cshtml
) とは異なり、Blazor では、コンポーネントのレンダリング中に Razor 式で非同期処理を実行することはできません。 これは、Blazor が対話型 UI をレンダリングするように設計されているためです。 対話型 UI の場合、画面には常に何かが表示されている必要があるため、レンダリング フローをブロックしても意味はありません。 代わりに、非同期処理は、いずれかの非同期ライフサイクル イベント中に実行されます。 非同期ライフサイクル イベントが発生するたびに、コンポーネントは再びレンダリングされる可能性があります。 次の Razor 構文はサポートされていません。
<ParameterChild Title="@await ..." />
上記の例に含まれるコードでは、アプリのビルド時に "コンパイラ エラー" が発生します。
'await' 演算子は、非同期メソッド内でのみ使用できます。 このメソッドを 'async' 修飾子でマークし、その戻り値の型を 'Task' に変更することを検討してください。
上記の例で非同期的に Title
パラメーターの値を取得するには、次の例に示すように、コンポーネントで OnInitializedAsync
ライフサイクル イベントを使用できます。
<ParameterChild Title="@title" />
@code {
private string title;
protected override async Task OnInitializedAsync()
{
title = await ...;
}
}
詳しくは、「ASP.NET Core Razor コンポーネントのライフサイクル」をご覧ください。
パラメーターに代入するために、明示的な Razor 式を使用してテキストを式の結果と連結することは、サポートされていません。 次の例では、テキスト "Set by
" とオブジェクトのプロパティ値を連結しようとしています。 この構文は Razor ページ (.cshtml
) でサポートされていますが、コンポーネントで子の Title
パラメーターに代入する場合は無効です。 次の Razor 構文はサポートされていません。
<ParameterChild Title="Set by @(panelData.Title)" />
上記の例に含まれるコードでは、アプリのビルド時に "コンパイラ エラー" が発生します。
コンポーネント属性では、複合コンテンツ (C# とマークアップの混合) はサポートされていません。
合成値の割り当てをサポートするには、メソッド、フィールド、またはプロパティを使用します。 次の例では、C# メソッド GetTitle
で、"Set by
" とオブジェクトのプロパティ値の連結を実行します。
Pages/ParameterParent3.razor
:
@page "/parameter-parent-3"
<ParameterChild Title="@GetTitle()" />
@code {
private PanelData panelData = new PanelData();
private string GetTitle() => $"Set by {panelData.Title}";
private class PanelData
{
public string Title { get; set; } = "Parent";
}
}
詳細については、「ASP.NET Coreの Razor 構文リファレンス」を参照してください。
警告
コンポーネント パラメーターの初期値の指定はサポートされていますが、コンポーネントが初めてレンダリングされた後に独自のパラメーターに書き込むコンポーネントは作成しないでください。 詳細については、この記事の「上書きされたパラメーター」セクションをご覧ください。
コンポーネント パラメーターは "自動プロパティ" として宣言する必要があります。つまり、それらの get
や set
アクセサーにカスタム ロジックを含めることはできません。 たとえば、次の StartData
プロパティは自動プロパティです。
[Parameter]
public DateTime StartData { get; set; }
get
または set
アクセサーにカスタム ロジックを配置しないでください。なぜなら、コンポーネント パラメーターの目的は、親コンポーネントから子コンポーネントに情報をフローさせるためのチャネルとして使用することだけだからです。 子コンポーネントのプロパティの set
アクセサーに、親コンポーネントの再レンダリングが発生する原因となるロジックが含まれている場合、レンダリングの無限ループが発生します。
受け取ったパラメーター値を変換するには、次のようにします。
- パラメーター プロパティは、指定された生データを表す自動プロパティのままにしておきます。
- 異なるプロパティまたはメソッドを作成し、パラメーター プロパティに基づいて変換されたデータを指定します。
OnParametersSetAsync
をオーバーライドし、新しいデータを受け取るたびに受け取ったパラメーターを変換します。
コンポーネント パラメーターに初期値を書き込むことはサポートされています。これは、初期値の代入によって、Blazor の自動コンポーネント レンダリングが妨げられることはないからです。 DateTime.Now での現在のローカル DateTime の StartData
への次の代入は、コンポーネントで有効な構文です。
[Parameter]
public DateTime StartData { get; set; } = DateTime.Now;
DateTime.Now の初期代入後は、開発者コードで StartData
に値を代入しないでください。 詳細については、この記事の「上書きされたパラメーター」セクションをご覧ください。
ルート パラメーター
コンポーネントでは、@page
ディレクティブのルート テンプレートでルート パラメーターを指定できます。 Blazor ルーターでは、ルート パラメーターを使用して、対応するコンポーネント パラメーターが設定されます。
Pages/RouteParameter.razor
:
@page "/route-parameter"
@page "/route-parameter/{text}"
<h1>Blazor is @Text!</h1>
@code {
[Parameter]
public string Text { get; set; }
protected override void OnInitialized()
{
Text = Text ?? "fantastic";
}
}
省略可能なルート パラメーターはサポートされていないため、前の例では 2 つの @page
ディレクティブが適用されます。 1 つ目の @page
ディレクティブでは、ルート パラメーターを指定せずにコンポーネントへの移動を許可します。 2 番目の @page
ディレクティブは、{text}
ルート パラメーターを受け取り、その値を Text
プロパティに割り当てます。
複数のフォルダー境界を越えるパスをキャプチャするキャッチオール ルート パラメーター ({*pageRoute}
) について詳しくは、「ASP.NET Core の Blazor ルーティングとナビゲーション」をご覧ください。
子コンテンツのレンダリング フラグメント
コンポーネントでは、別のコンポーネントのコンテンツを設定できます。 代入コンポーネントでは、子コンポーネントの開始および終了タグの間にコンテンツを指定します。
次の例では、RenderFragmentChild
コンポーネントに、RenderFragment としてレンダリングする UI のセグメントを表す ChildContent
コンポーネント パラメーターがあります。 コンポーネントの Razor マークアップ内の ChildContent
の位置は、コンテンツが最終的な HTML 出力にレンダリングされる場所です。
Shared/RenderFragmentChild.razor
:
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Child content</div>
<div class="card-body">@ChildContent</div>
</div>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
}
重要
RenderFragment コンテンツを受け取るプロパティは、規則によって ChildContent
という名前にする必要があります。
イベント コールバックは、RenderFragment ではサポートされていません。
次の RenderFragmentParent
コンポーネントでは、子コンポーネントの開始および終了タグ内にコンテンツを配置することによって RenderFragmentChild
をレンダリングするためのコンテンツを指定します。
Pages/RenderFragmentParent.razor
:
@page "/render-fragment-parent"
<h1>Render child content</h1>
<RenderFragmentChild>
Content of the child component is supplied
by the parent component.
</RenderFragmentChild>
Blazor による子コンテンツのレンダリング方法により、for
ループ内のコンポーネントのレンダリングでは、インクリメントするループ変数が RenderFragmentChild
コンポーネントのコンテンツ内で使用されている場合、ローカル インデックス変数が必要になります。 次の例は、前の RenderFragmentParent
コンポーネントに追加できます。
<h1>Three children with an index variable</h1>
@for (int c = 0; c < 3; c++)
{
var current = c;
<RenderFragmentChild>
Count: @current
</RenderFragmentChild>
}
または、for
ループではなく、Enumerable.Range と共に foreach
ループを使用します。 次の例は、前の RenderFragmentParent
コンポーネントに追加できます。
<h1>Second example of three children with an index variable</h1>
@foreach (var c in Enumerable.Range(0,3))
{
<RenderFragmentChild>
Count: @c
</RenderFragmentChild>
}
レンダリング フラグメントは、Blazor アプリ全体で子コンテンツをレンダリングするために使用され、次の記事と記事のセクションの例と共に説明されています。
注意
Blazor フレームワークの組み込み Razor コンポーネントでは、それぞれ同じ ChildContent
コンポーネント パラメーター規則を使用してコンテンツを設定します。 API ドキュメントでコンポーネント パラメーター プロパティ名ChildContent
を検索することにより、子コンテンツを設定しているコンポーネントを確認できます (検索用語 "保育" を使用して API をフィルター処理)。
再利用可能なレンダリング ロジックのフラグメントをレンダリングする
レンダリング ロジックを再利用するための方法として、子コンポーネントを純粋に取り出すことができます。 任意のコンポーネントの @code
ブロックで、RenderFragment を定義し、必要に応じて任意の場所からフラグメントをレンダリングします。
<h1>Hello, world!</h1>
@RenderWelcomeInfo
<p>Render the welcome info a second time:</p>
@RenderWelcomeInfo
@code {
private RenderFragment RenderWelcomeInfo = __builder =>
{
<p>Welcome to your new app!</p>
};
}
詳細については、レンダリング ロジックの再利用に関するページを参照してください。
上書きされたパラメーター
Blazor フレームワークでは、一般に安全な親から子へのパラメーターの割り当てが行われます。
- パラメーターが予期せずに上書きされることはありません。
- 副作用は最小限に抑えられます。 たとえば、追加のレンダリングは、無限のレンダリング ループが作成される可能性があるため、回避されます。
子コンポーネントは、親コンポーネントのレンダリング時に既存の値を上書きする可能性がある新しいパラメーター値を受け取ります。 子コンポーネントでパラメーター値が誤って上書きされることは、1 つまたは複数のデータバインド パラメーターを使用してコンポーネントを開発しており、開発者が子のパラメーターに直接書き込む場合に多く発生します。
- 子コンポーネントは、親コンポーネントの 1 つまたは複数のパラメーター値を使用してレンダリングされます。
- 子によって、パラメーターの値が直接書き込まれます。
- 親コンポーネントがレンダリングされ、子のパラメーターの値が上書きされます。
パラメーター値の上書きの可能性は、子コンポーネントのプロパティ set
アクセサーにも及びます。
重要
一般的なガイダンスとして、コンポーネントが初めてレンダリングされた後に、独自のパラメーターに直接書き込むコンポーネントを作成しないでください。
次が実行される Expander
コンポーネントについて考えてみましょう。
- 子コンテンツのレンダリング。
- コンポーネント パラメーター (
Expanded
) を使用した、子コンテンツの表示の切り替え。
次の Expander
コンポーネントで上書きされるパラメーターを示した後に、このシナリオの正しい方法を示すために変更した Expander
コンポーネントを紹介します。 説明されている動作を体験するために、次の例をローカルのサンプル アプリで使用することができます。
Shared/Expander.razor
:
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
<div class="card-body">
<h2 class="card-title">Toggle (<code>Expanded</code> = @Expanded)</h2>
@if (Expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>
@code {
[Parameter]
public bool Expanded { private get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
private void Toggle()
{
Expanded = !Expanded;
}
}
Expander
コンポーネントは、StateHasChanged を呼び出す可能性のある次の ExpanderExample
親コンポーネントに追加されます。
- 開発者コードで StateHasChanged を呼び出すと、その状態が変更されたことがコンポーネントに通知され、通常は UI を更新するためにコンポーネントの再レンダリングがトリガーされます。 StateHasChanged について詳しくは、「ASP.NET Core Razor コンポーネントのライフサイクル」と「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。
- ボタンの
@onclick
ディレクティブ属性により、イベント ハンドラーがそのボタンのonclick
イベントにアタッチされます。 イベント処理について詳しくは、「ASP.NET Core Blazor のイベント処理」をご覧ください。
Pages/ExpanderExample.razor
:
@page "/expander-example"
<Expander Expanded="true">
Expander 1 content
</Expander>
<Expander Expanded="true" />
<button @onclick="StateHasChanged">
Call StateHasChanged
</button>
初期状態では、Expanded
プロパティが切り替えられると、Expander
コンポーネントはそれぞれ独立して動作します。 子コンポーネントの状態は、想定どおりのままです。
親コンポーネント内で StateHasChanged が呼び出されると、それらのパラメーターが変更された可能性がある場合に、Blazor フレームワークは子コンポーネントを再レンダリングします。
- Blazor が明示的に確認するパラメーター型のグループに対して、パラメーターのいずれかが変更されたことを Blazor が検出した場合、子コンポーネントを再レンダリングします。
- 確認されていないパラメーター型に対しては、"パラメーターが変更されたかどうかに関係なく"、Blazor は子コンポーネントを再レンダリングします。 子コンテンツは、このカテゴリのパラメーター型に分類されます。子コンテンツは、他の変更可能なオブジェクトを参照するデリゲートである型 RenderFragment だからです。
ExpanderExample
コンポーネントの場合:
- 1 つ目の
Expander
コンポーネントで、潜在的に変更可能な RenderFragment 内の子コンテンツを設定します。そのため、親コンポーネント内で StateHasChanged を呼び出すと、自動的にコンポーネントが再レンダリングされ、Expanded
の値がその初期値であるtrue
に上書きされる可能性があります。 - 2 つ目の
Expander
コンポーネントでは、子コンテンツを設定しません。 そのため、潜在的に変更可能な RenderFragment は存在しません。 親コンポーネントで StateHasChanged を呼び出しても、子コンポーネントは自動的に再レンダリングされないので、コンポーネントのExpanded
の値は上書きされません。
前のシナリオでの状態を維持するには、Expander
コンポーネントで "プライベート フィールド" を使用して、切り替え状態を維持します。
次の変更された Expander
コンポーネント:
- 親から
Expanded
コンポーネント パラメーター値を受け入れます。 - コンポーネント パラメーター値を、
OnInitialized
イベントの "プライベート フィールド" (expanded
) に割り当てます。 - プライベート フィールドを使用して、その内部のトグル状態を維持します。これは、パラメーターに直接書き込まれないようにする方法を示しています。
Note
このセクションのアドバイスは、コンポーネント パラメーター set
アクセサーの同様のロジックに及ぶため、同様の望ましくない副作用が発生する可能性があります。
Shared/Expander.razor
:
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
<div class="card-body">
<h2 class="card-title">Toggle (<code>expanded</code> = @expanded)</h2>
@if (expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>
@code {
private bool expanded;
[Parameter]
public bool Expanded { private get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
protected override void OnInitialized()
{
expanded = Expanded;
}
private void Toggle()
{
expanded = !expanded;
}
}
詳細については、Blazor両方向のバインド エラー (dotnet/aspnetcore #24599) に関するページを参照してください。
Blazor によって確認される正確な型の情報など、変更検出の詳細については、「ASP.NET Core Razor コンポーネントのレンダリング」を参照してください。
属性スプラッティングと任意のパラメーター
コンポーネントでは、コンポーネントの宣言されたパラメーターに加えて、追加の属性をキャプチャしてレンダリングできます。 追加の属性は、ディクショナリに取り込んでから、@attributes
Razor ディレクティブ属性を使用してコンポーネントがレンダリングされるときに要素に "スプラッティング" できます。 このシナリオは、さまざまなカスタマイズをサポートするマークアップ要素を生成するコンポーネントを定義する場合に便利です。 たとえば、多くのパラメーターをサポートする <input>
に対して、属性を個別に定義するのは面倒な場合があります。
次の Splat
コンポーネントでは、以下のことを行います。
- 1 つ目の
<input>
要素 (id="useIndividualParams"
) では、個々のコンポーネント パラメーターを使用します。 - 2 つ目の
<input>
要素 (id="useAttributesDict"
) では、属性のスプラッティングを使用します。
Pages/Splat.razor
:
@page "/splat"
<input id="useIndividualParams"
maxlength="@maxlength"
placeholder="@placeholder"
required="@required"
size="@size" />
<input id="useAttributesDict"
@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 Dictionary<string, object>()
{
{ "maxlength", "10" },
{ "placeholder", "Input placeholder text" },
{ "required", "required" },
{ "size", "50" }
};
}
Web ページにレンダリングされる <input>
要素は同じです。
<input id="useIndividualParams"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">
<input id="useAttributesDict"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">
任意の属性を受け入れるには、CaptureUnmatchedValues プロパティを true
に設定したコンポーネント パラメーターを定義します。
@code {
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> InputAttributes { get; set; }
}
[Parameter]
の CaptureUnmatchedValues プロパティにより、パラメーターを他のパラメーターと一致しないすべての属性と一致させることができます。 1 つのコンポーネントで、CaptureUnmatchedValues を持つパラメーターは 1 つだけ定義できます。 CaptureUnmatchedValues で使用されるプロパティの型は、文字列キーを使用して Dictionary<string, object>
から割り当て可能である必要があります。 このシナリオでは、IEnumerable<KeyValuePair<string, object>>
または IReadOnlyDictionary<string, object>
も使用できます。
要素属性の位置を基準とした @attributes
の位置は重要です。 @attributes
が要素にスプラッティングされると、属性は右から左 (最後から最初) に処理されます。 子コンポーネントを使用する次の親コンポーネントの例を考えてみます。
Shared/AttributeOrderChild1.razor
:
<div @attributes="AdditionalAttributes" extra="5" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; }
}
Pages/AttributeOrderParent1.razor
:
@page "/attribute-order-parent-1"
<AttributeOrderChild1 extra="10" />
AttributeOrderChild1
コンポーネントの extra
属性が @attributes
の右側に設定されています。 属性は右から左 (最後から最初) に処理されるため、追加の属性によって渡された場合に、AttributeOrderParent1
コンポーネントのレンダリングされる <div>
に、extra="5"
が含まれます。
<div extra="5" />
次の例では、子コンポーネントの <div>
で extra
と @attributes
の順序が逆になります。
Shared/AttributeOrderChild2.razor
:
<div extra="5" @attributes="AdditionalAttributes" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; }
}
Pages/AttributeOrderParent2.razor
:
@page "/attribute-order-parent-2"
<AttributeOrderChild2 extra="10" />
追加の属性によって渡された場合に、親コンポーネントのレンダリングされる Web ページの <div>
には extra="10"
が含まれます。
<div extra="10" />
コンポーネントへの参照をキャプチャする
コンポーネント参照を使用すると、コマンドを発行するためのコンポーネント インスタンスを参照することができます。 コンポーネント参照をキャプチャするには:
- 子コンポーネントに
@ref
属性を追加します。 - 子コンポーネントと同じ型のフィールドを定義します。
コンポーネントがレンダリングされると、フィールドにコンポーネント インスタンスが設定されます。 その後、そのインスタンスに対して .NET メソッドを呼び出すことができます。
ChildMethod
が呼び出されたときにメッセージをログに記録する次の ReferenceChild
コンポーネントについて考えてみます。
Shared/ReferenceChild.razor
:
@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> logger
@code {
public void ChildMethod(int value)
{
logger.LogInformation("Received {Value} in ChildMethod", value);
}
}
コンポーネント参照は、コンポーネントがレンダリングされた後にのみ設定され、その出力には ReferenceChild
要素が含まれます。 コンポーネントがレンダリングされるまで、参照するものはありません。
コンポーネントのレンダリングが終了した後にコンポーネント参照を操作するには、OnAfterRender
または OnAfterRenderAsync
メソッドを使用します。
イベント ハンドラーで参照変数を使用するには、ラムダ式を使用するか、OnAfterRender
または OnAfterRenderAsync
メソッドでイベント ハンドラー デリゲートを割り当てます。 これにより、イベント ハンドラーが割り当てられる前に参照変数が確実に割り当てられます。
次のラムダ手法では、前の ReferenceChild
コンポーネントを使用します。
Pages/ReferenceParent1.razor
:
@page "/reference-parent-1"
<button @onclick="@(() => childComponent.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild childComponent;
}
次のデリゲート手法では、上記の ReferenceChild
コンポーネントを使用します。
Pages/ReferenceParent2.razor
:
@page "/reference-parent-2"
<button @onclick="callChildMethod">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild childComponent;
private Action callChildMethod;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
callChildMethod = CallChildMethod;
}
}
private void CallChildMethod()
{
childComponent.ChildMethod(5);
}
}
コンポーネント参照の取り込みでは、要素参照の取り込みと同様の構文を使用しますが、コンポーネント参照の取り込みは JavaScript 相互運用機能ではありません。 コンポーネント参照は、JavaScript コードに渡されません。 コンポーネント参照は、.NET コードでのみ使用されます。
重要
子コンポーネントの状態を変えるためにコンポーネント参照を使用しないでください。 代わりに、通常の宣言型コンポーネント パラメーターを使用して、子コンポーネントにデータを渡します。 コンポーネント パラメーターを使用すると、子コンポーネントが正しいタイミングで自動的にレンダリングされます。 詳しくは、「コンポーネントのパラメーター」セクションと、記事「ASP.NET Core Blazor データ バインディング」をご覧ください。
同期コンテキスト
Blazor では、同期コンテキスト (SynchronizationContext) を使用して、1 つの実行の論理スレッドを強制します。 コンポーネントのライフサイクル メソッドと、Blazor によって発生するイベント コールバックは、同期コンテキストで実行されます。
Blazor Server の同期コンテキストでは、ブラウザーの WebAssembly モデル (シングル スレッド) と厳密に一致するように、シングルスレッド環境のエミュレートが試行されます。 どの時点でも、作業は 1 つのスレッドでのみ実行され、1 つの論理スレッドであるという印象になります。 2 つの操作が同時に実行されることはありません。
スレッドをブロックする呼び出しを避ける
一般に、コンポーネントでは次のメソッドは呼び出さないでください。 次のメソッドでは実行スレッドがブロックされます。そのため、基になる Task が完了するまで、アプリの動作が再開されなくなります。
Note
このセクションに示されているスレッド ブロック メソッドを使用する Blazor ドキュメントの例では、推奨されるコーディング ガイダンスとしてではなく、デモンストレーション目的でのみメソッドを使用しています。 たとえば、いくつかのコンポーネント コードのデモでは、Thread.Sleep を呼び出して、実行時間の長いプロセスをシミュレートします。
状態を更新するために外部でコンポーネント メソッドを呼び出す
タイマーやその他の通知などの外部イベントに基づいてコンポーネントを更新する必要がある場合は、InvokeAsync
メソッドを使用します。これにより、Blazor の同期コンテキストにコードの実行がディスパッチされます。 たとえば、リッスンしているコンポーネントに、更新状態について通知できる次の ''通知サービス'' を考えてみます。 Update
メソッドは、アプリ内のどこからでも呼び出すことができます。
TimerService.cs
:
using System;
using System.Timers;
using Microsoft.Extensions.Logging;
public class TimerService : IDisposable
{
private int elapsedCount;
private readonly ILogger<TimerService> logger;
private readonly NotifierService notifier;
private Timer timer;
public TimerService(NotifierService notifier, ILogger<TimerService> logger)
{
this.notifier = notifier;
this.logger = logger;
}
public void Start()
{
if (timer is null)
{
timer = new();
timer.AutoReset = true;
timer.Interval = 10000;
timer.Elapsed += HandleTimer;
timer.Enabled = true;
logger.LogInformation("Started");
}
}
private async void HandleTimer(object source, ElapsedEventArgs e)
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation($"elapsedCount: {elapsedCount}");
}
public void Dispose()
{
timer?.Dispose();
}
}
NotifierService.cs
:
using System;
using System.Threading.Tasks;
public class NotifierService
{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}
public event Func<string, int, Task> Notify;
}
サービスを登録します。
Blazor WebAssembly アプリでは、
Program.cs
のシングルトンとしてサービスを登録します。builder.Services.AddSingleton<NotifierService>(); builder.Services.AddSingleton<TimerService>();
Blazor Server アプリでは、
Startup.ConfigureServices
を範囲としてサービスを登録します。services.AddScoped<NotifierService>(); services.AddScoped<TimerService>();
NotifierService
を使用して、コンポーネントを更新します。
Pages/ReceiveNotifications.razor
:
@page "/receive-notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer
<h1>Receive Notifications</h1>
<h2>Timer Service</h2>
<button @onclick="StartTimer">Start Timer</button>
<h2>Notifications</h2>
<p>
Status:
@if (lastNotification.key is not null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting first notification</span>
}
</p>
@code {
private (string key, int value) lastNotification;
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public async Task OnNotify(string key, int value)
{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}
private void StartTimer()
{
Timer.Start();
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
前の例の場合:
- Blazor の同期コンテキスト外で
NotifierService
からコンポーネントのOnNotify
メソッドが呼び出されます。InvokeAsync
を使用して、正しいコンテキストに切り替え、レンダリングをキューに登録します。 詳しくは、「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。 - コンポーネントでは IDisposable を実装します。
OnNotify
デリゲートのサブスクライブはDispose
メソッドで解除されます。このメソッドは、コンポーネントが破棄されたときにフレームワークによって呼び出されます。 詳しくは、「ASP.NET Core Razor コンポーネントのライフサイクル」をご覧ください。
要素またはコンポーネントの保持の制御での @key
の使用
要素またはコンポーネントのリストをレンダリングし、その後に要素またはコンポーネントが変更された場合、Blazor では、前のどの要素やコンポーネントを保持できるか、およびモデル オブジェクトをそれらにどのようにマップするかを決定する必要があります。 通常、このプロセスは自動で、無視できますが、プロセスの制御が必要になる場合があります。
次の Details
および People
コンポーネントについて考えてみます。
Details
コンポーネントでは、<input>
要素に表示される親のPeople
コンポーネントからデータ (Data
) を受け取ります。 表示される任意の<input>
要素では、<input>
要素のいずれかを選択すると、ユーザーからページのフォーカスを受け取ることができます。People
コンポーネントでは、Details
コンポーネントを使用して表示する person オブジェクトのリストを作成します。 3 秒ごとに、新しい個人がコレクションに追加されます。
このデモで次のことを行うことができます。
- レンダリングされた複数の
Details
コンポーネントの中から<input>
を選択する。 - people コレクションの自動拡大時のページのフォーカスの動作を調べる。
Shared/Details.razor
:
<input value="@Data" />
@code {
[Parameter]
public string Data { get; set; }
}
次の People
コンポーネントでは、個人を OnTimerCallback
に追加する各イテレーションによって、Blazor でコレクション全体がリビルドされます。 ページのフォーカスは、<input>
要素の ''同じインデックス'' 位置に留まります。そのため、個人が追加されるたびにフォーカスが移動します。 ''ユーザーが選択した内容からフォーカスを移動することは、望ましい動作ではありません。 '' 次のコンポーネントでの不適切な動作を示した後、ユーザーのエクスペリエンスを向上させるために @key
ディレクティブ属性が使用されます。
Pages/People.razor
:
@page "/people"
@using System.Timers
@implements IDisposable
@foreach (var person in people)
{
<Details Data="@person.Data" />
}
@code {
private Timer timer = new Timer(3000);
public List<Person> people =
new List<Person>()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss tt")}"
});
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
public class Person
{
public string Data { get; set; }
}
}
people
コレクションのコンテンツは、挿入、削除、または順序変更されたエントリによって変更されます。 再レンダリングによって、表示動作に違いが生じる可能性があります。 people
コレクションに個人が挿入されるたびに、現在フォーカスがある要素の ''前の要素'' でフォーカスを受け取ります。 ユーザーのフォーカスは失われます。
要素またはコンポーネントのコレクションへのマッピング プロセスは、@key
ディレクティブ属性を使用して制御できます。 @key
を使用すると、キーの値に基づいて要素またはコンポーネントが確実に保持されます。 前の例の Details
コンポーネントが person
項目にキー指定されている場合、Blazor では、変更されていない Details
コンポーネントのレンダリングが無視されます。
people
コレクションで @key
ディレクティブ属性を使用するように People
コンポーネントを変更するには、<Details>
要素を次のように更新します。
<Details @key="person" Data="@person.Data" />
people
コレクションが変更されても、Details
インスタンスと person
インスタンス間の関連付けは保持されます。 コレクションの先頭に Person
が挿入されると、その対応する位置に 1 つの新しい Details
インスタンスが挿入されます。 他のインスタンスは変更されません。 そのため、コレクションに人々が追加されても、ユーザーのフォーカスは失われません。
@key
ディレクティブ属性が使用されている場合、他のコレクションの更新でも同じ動作になります。
- コレクションからインスタンスが削除された場合、対応するコンポーネント インスタンスのみが UI から除去されます。 他のインスタンスは変更されません。
- コレクション エントリの順序が変更された場合、対応するコンポーネント インスタンスは UI で保持され、順序が変更されます。
重要
キーは、各コンテナー要素やコンポーネントに対してローカルです。 キーはドキュメント全体でグローバルに比較されません。
どのようなときに @key
を使用するか
一般に、リストがレンダリングされ (たとえば、foreach
ブロックで)、@key
を定義するための適切な値が存在する場合は常に、@key
を使用することは意味があります。
次の例に示すように、@key
を使用して、オブジェクトが変更されない場合に要素またはコンポーネントのサブツリーを保持することもできます。
例 1:
<li @key="person">
<input value="@person.Data" />
</li>
例 2:
<div @key="person">
@* other HTML elements *@
</div>
person
インスタンスが変更された場合、@key
属性ディレクティブにより、Blazor に次のことが強制されます。
<li>
または<div>
の全体およびその子孫を破棄する。- 新しい要素とコンポーネントを使用して、UI 内でサブツリーをリビルドする。
これは、コレクションがサブツリー内で変更されたときに UI の状態が確実に保持されないようにするのに役立ちます。
@key
のスコープ
@key
属性のディレクティブのスコープは、その親内の自身の兄弟です。
次の例を考えてみます。 first
および second
キーは、外側の <div>
要素内の同じスコープで互いに比較されます。
<div>
<div @key="first">...</div>
<div @key="second">...</div>
</div>
次の例では、互いに関係がなく、互いに影響を与えることのない、独自のスコープの first
と second
キーを示しています。 各 @key
のスコープはその親 <div>
要素のみであり、各親 <div>
要素ではありません。
<div>
<div @key="first">...</div>
</div>
<div>
<div @key="second">...</div>
</div>
前に示した Details
コンポーネントの場合、次の例では、同じ @key
のスコープ内の person
データがレンダリングされます。これが @key
の一般的な使用例です。
<div>
@foreach (var person in people)
{
<Details @key="person" Data="@person.Data" />
}
</div>
@foreach (var person in people)
{
<div @key="person">
<Details Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li @key="person">
<Details Data="@person.Data" />
</li>
}
</ol>
次の例の @key
のスコープは、各 Details
コンポーネント インスタンスを囲む、<div>
または <li>
要素のみです。 つまり、people
コレクションの各メンバーの person
データは、レンダリングされた各 Details
コンポーネントの各 person
インスタンスのキーではありません。 @key
を使用する場合は、次のパターンを避けてください。
@foreach (var person in people)
{
<div>
<Details @key="person" Data="@person.Data" />
</div>
}
<ol>
@foreach (var person in people)
{
<li>
<Details @key="person" Data="@person.Data" />
</li>
}
</ol>
どのようなときに @key
を使用しないか
@key
でレンダリングすると、パフォーマンスが低下します。 パフォーマンスの低下は大きくありませんが、要素やコンポーネントを保持することによって、アプリにメリットがある場合にのみ @key
を指定してください。
@key
を使用しない場合でも、Blazor では可能な限り、子要素とコンポーネント インスタンスが保持されます。 @key
を使用する唯一の利点は、マッピングを選択する Blazor ではなく、保持されているコンポーネント インスタンスにモデル インスタンスをマップする "方法" が制御されることです。
@key
に使用する値
一般に、@key
には、次のいずれかの値を指定するのが適切です。
- モデル オブジェクト インスタンス。 たとえば、前の例では
Person
インスタンス (person
) が使用されていました。 これにより、オブジェクト参照の等価性に基づいて保持されます。 - 一意識別子。 たとえば、一意識別子は
int
、string
、またはGuid
型の主キー値を基にすることができます。
@key
に使用される値は競合しないようにしてください。 同じ親要素内で競合する値が検出された場合、Blazor では、古い要素やコンポーネントを新しい要素やコンポーネントに確定的にマップできないため、例外がスローされます。 個別の値 (オブジェクト インスタンスや主キー値など) のみを使用してください。
属性を適用する
属性は、@attribute
ディレクティブを使用してコンポーネントに適用できます。 次の例では、[Authorize]
属性をコンポーネントのクラスに適用しています。
@page "/"
@attribute [Authorize]
条件付き HTML 要素属性
HTML 要素属性プロパティは、.NET 値に基づいて条件付きで設定されます。 値が false
または null
の場合、プロパティは設定されません。 値が true
の場合は、プロパティが設定されます。
次の例では、IsCompleted
により、<input>
要素の checked
プロパティが設定されるかどうかが決定されます。
Pages/ConditionalAttribute.razor
:
@page "/conditional-attribute"
<label>
<input type="checkbox" checked="@IsCompleted" />
Is Completed?
</label>
<button @onclick="@(() => IsCompleted = !IsCompleted)">
Change IsCompleted
</button>
@code {
[Parameter]
public bool IsCompleted { get; set; }
}
詳細については、「ASP.NET Coreの Razor 構文リファレンス」を参照してください。
警告
.NET 型が bool
の場合、aria-pressed
などの一部の HTML 属性が正しく機能しません。 そのような場合は、bool
ではなく string
型を使用します。
生 HTML
通常、文字列は DOM テキスト ノードを使用してレンダリングされます。つまり、それらに含まれている可能性のあるすべてのマークアップが無視され、リテラル テキストとして扱われます。 生 HTML をレンダリングするには、HTML コンテンツを MarkupString 値にラップします。 値は HTML または SVG として解析され、DOM に挿入されます。
警告
信頼されていないソースから構築された生 HTML をレンダリングすることは、セキュリティ リスクであるため、常に避ける必要があります。
次の例では、MarkupString 型を使用して、コンポーネントのレンダリングされた出力に静的 HTML コンテンツのブロックを追加しています。
Pages/MarkupStringExample.razor
:
@page "/markup-string-example"
@((MarkupString)myMarkup)
@code {
private string myMarkup =
"<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
}
Razor テンプレート
レンダリング フラグメントは、UI スニペットを定義するための Razor テンプレート構文を使用して定義できます。 Razor テンプレートでは次の形式を使用します。
@<{HTML tag}>...</{HTML tag}>
次の例では、RenderFragment と RenderFragment<TValue> の値を指定し、コンポーネント内にテンプレートを直接レンダリングする方法を示しています。 レンダリング フラグメントは、引数としてテンプレート コンポーネントに渡すこともできます。
Pages/RazorTemplate.razor
:
@page "/razor-template"
@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; }
}
}
前のコードのレンダリングされた結果:
<p>The time is 4/19/2021 8:54:46 AM.</p>
<p>Pet: Nutty Rex</p>
静的な資産
静的資産の場合、Blazor では ASP.NET Core アプリの規則に従います。 静的資産は、プロジェクトの web root
(wwwroot
) フォルダー、または wwwroot
フォルダーの下のフォルダーにあります。
静的アセットの Web ルートを参照するには、ベース相対パス (/
) を使用します。 次の例では、logo.png
が物理的に {PROJECT ROOT}/wwwroot/images
フォルダーに配置されています。 {PROJECT ROOT}
は、アプリのプロジェクト ルートです。
<img alt="Company logo" src="/images/logo.png" />
コンポーネントでは、チルダ スラッシュ表記 (~/
) はサポートされていません。
アプリのベース パスの設定について詳しくは、「ASP.NET Core Blazor のホストと展開」をご覧ください。
タグ ヘルパーはコンポーネントでサポートされない
Tag Helpers
はコンポーネントでサポートされていません。 Blazor にタグ ヘルパーのような機能を提供するには、タグ ヘルパーと同じ機能を持つコンポーネントを作成し、代わりにそのコンポーネントを使用します。
スケーラブル ベクター グラフィックス (SVG) イメージ
Blazor は HTML をレンダリングするため、スケーラブル ベクター グラフィックス (SVG) 画像 (.svg
) などのブラウザーでサポートされている画像は、<img>
タグを介してサポートされます。
<img alt="Example image" src="image.svg" />
同様に、SVG 画像は、スタイルシート ファイル (.css
) の CSS 規則でサポートされています。
.element-class {
background-image: url("image.svg");
}
空白文字のレンダリング動作
空白文字は、コンポーネントのソース マークアップに保持されます。 空白文字のみのテキストは、視覚効果がないときでも、ブラウザーの DOM にレンダリングされます。
次のコンポーネント マークアップについて考えてみます。
<ul>
@foreach (var item in Items)
{
<li>
@item.Text
</li>
}
</ul>
前の例では、次の不要な空白文字がレンダリングされます。
@foreach
コード ブロックの外側。<li>
要素の前後。@item.Text
出力の前後。
100 項目のリストの場合、空白文字の領域が 400 を超えます。 レンダリングされる出力に視覚的に影響する余分な空白文字はありません。
コンポーネントの静的 HTML をレンダリングする場合、タグ内の空白文字は保持されません。 たとえば、コンポーネント <img>
ファイル (.razor
) で次の Razor タグのレンダリングされる出力を表示します。
<img alt="Example image" src="img.png" />
前のマークアップからの空白文字は保持されません。
<img alt="Example image" src="img.png" />
ジェネリック型パラメーターのサポート
@typeparam
ディレクティブによって、生成されるコンポーネント クラスのジェネリック型パラメーターを宣言します。
@typeparam TItem
次の例では、ListGenericTypeItems1
コンポーネントは TExample
としてジェネリック型に指定されます。
Shared/ListGenericTypeItems1.razor
:
@typeparam TExample
@if (ExampleList != null)
{
<ul>
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}
@code {
[Parameter]
public IEnumerable<TExample> ExampleList{ get; set; }
}
次の GenericTypeExample1
コンポーネントでは、2 つの ListGenericTypeItems1
コンポーネントがレンダリングされます。
- 文字列または整数のデータは、各コンポーネントの
ExampleList
パラメーターに割り当てられます。 - 割り当てられたデータの型に一致する型
string
またはint
が、各コンポーネントの型パラメーター (TExample
) に設定されます。
Pages/GenericTypeExample1.razor
:
@page "/generic-type-example-1"
<h1>Generic Type Example 1</h1>
<ListGenericTypeItems1 ExampleList="@(new List<string> { "Item 1", "Item 2" })"
TExample="string" />
<ListGenericTypeItems1 ExampleList="@(new List<int> { 1, 2, 3 })"
TExample="int" />
詳細については、次の記事を参照してください。