Blazor ComponentのLayout機能をフル活用しよう！


Layoutという仕組みを使って、共通ヘッダーを定義するやり方を説明します。

[公式ドキュメント](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/layouts?view=aspnetcore-8.0)に沿いつつご紹介します。

## 環境

VS2022のBlazor Appテンプレートからプロジェクトを作成します。

Render Modeはどれを選択しても大丈夫です。

## Layout Componentの作り方

`@inherits LayoutComponentBase`を継承してください。

`@Body`にComponentが描画されます。

シンプル化のため、本記事では記載していないですが、Layout Componentも他のComponentと同様に`@code`を持つことができますので、イベント処理なども当然行うことができます。

```c#:Custom Layout
@inherits LayoutComponentBase

<h1>Custom Layout</h1>

@Body
```

## Application全体に反映させたい場合

`Routes.razor`の`RouteView`に設定します。

```c#:Routes.razor
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }">
    <Found Context="routeData">
        @* DefaultLayoutに適用 *@
        <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.CustomLayout)" />
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>
</Router>
```

## フォルダごとに反映させたい場合

`_Imports.razor`ファイルの特性を活かします。

このファイルに記載されたDirectiveは、`_Imports.razor`ファイルが存在するフォルダと、そのフォルダ以下全てのRazor Componentに適用されます。

Directiveの一つとして、`@layout`をすることで、指定フォルダとその配下の**すべての`@page` を持つComponent**に、共通Layoutを適用させることができます。

例えば、管理者用 Layoutと一般ユーザー用 Layoutを分けたい場合は、以下のような実装で実現可能です。

- Layout 

```c#:AdminLayout.razor
@inherits LayoutComponentBase

<h1>Admin Layout</h1>

@Body
```

```c#:MemberLayout.razor
@inherits LayoutComponentBase

<h1>Member Layout</h1>

@Body
```

- `_Imports.razor`

```c#:Admin/_Imports.razor
@layout BlazorAppTips.Components.Layout.AdminLayout
```

```c#:Member/_Imports.razor
@layout BlazorAppTips.Components.Layout.MemberLayout
```

- Component

```c#:Admin/AdminComponent
@page "/admin"

<h3>Admin Component</h3>
```

```c#:Member/MemberComponent
@page "/member"

<h3>Member Component</h3>
```

- Tree

```Text:shell
├─Layout
│      AdminLayout.razor
│      MemberLayout.razor
└─Pages
    ├─Admin
    │      AdminComponent.razor
    │      _Imports.razor
    │
    └─Member
            MemberComponent.razor
            _Imports.razor
```

## 個別のComponentにLayoutを適用する場合

`@layout` Directiveを対象のComponentに記載すればOKです。

そのComponentのみにLayoutが適用されます。

## 特定のContentのみに適用する場合

`<LayoutView>` Componentを利用し、以下のように実装可能です。

例えば、Default Layoutを適用させつつ、一部のContentにのみ特定のLayoutを適用したいときに利用できます。

```c#:Compeont.razor        
@page "/"

<div>Home</div>

<LayoutView Layout="typeof(ErrorLayout)">
    <h1>Nested content</h1>
    <h2>Hello</h2>
</LayoutView>
```

## Nested Layouts

Lyout Componentに更に`@layout` Directiveを追加することで、Layoutを積み重ねることが可能です。

上述した、フォルダごと、ComponentごとのLayout適用は**Default Layoutを上書きします。**

例えば、を維持しつつ、フォルダごとにLayoutを適用させたいという場合は、以下のように記載することで実現できます。実務でも発生するケースですね。

フォルダごとにLayout Componentを適用させた場合、`<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.CustomLayout)" />`で指定されている`DefaultLayout`が上書きされるため、明示的にフォルダごとに適用されるLayout Componentに`@layout` Directiveを設定します。

- Layout

```c#:NestedLayout.razor
@inherits LayoutComponentBase
@layout CustomLayout

<h2>NestedLayout</h2>

@Body
```

- `_Imports.razor`

```c#:Nested/_Imports.razor
@layout BlazorAppTips.Components.Layout.NestedLayout
```

- Component

```c#:Nested/Component.razor
@page "/nested"

<h3>Component</h3>
```

- Tree

```Text:shell
├─Layout
│      CustomLayout.razor
│      NestedLayout.razor
└─Pages
    ├─Nested
    │      Component.razor
    │      _Imports.razor
```

## Layout Componentが見つからない？

### 名前空間

いくつかの方法で対象Layout(razor component)の名前空間を適用させることができます。

※`BlazorAppTips`がプロジェクト名の場合です。

- Root配下の_Imports.razorに`@using`を追加

```c#:_Imports.razor
@using BlazorAppTips.Components.Layout
```

- `@using`と`@layout`で名前空間とComponentを同時に指定

```C#:Component.razor
@using BlazorAppTips.Components.Layout
@layout CustomLayout
```

- `@layout`に名前空間とComponentをまとめて指定

```c#:Component
@layout BlazorAppTips.Components.Layout.CustomLayout
```


## 最後に

Layout Componentを利用することで、Componentの再利用性が高まります。

これにRender Modeが絡んでくると厄介で、Layout Component、`Routes.razor` Componentを`.Client`プロジェクト側に移動する必要などが出てきます。また別の機会にそれらのユースケースは説明できればと思います。

## リポジトリ

[BlazorAppTips](https://github.com/HIppei/BlazorAppTips)