悩んだこと
- アラート系のメッセージ処理を各ページでゴリゴリ書くのはイケてない
- 共通化して使いまわししたい
-
component
を作って使いまわすのにも限度があるし、MainLayout.razor
で一括に処理できればいいよね - 親コンポーネントと子コンポーネント間のやりとりってどうやるの?
今回の解決策
State Container
を作成し、それを介すことで親子間のStatus
のやり取りを行いました。Fluxの考え方に似た方法です。
できたもの
interface IStateMessageService
{
string StateMessages { get; }
event Action OnChange;
void ClearStateMessages();
void SetStateMessages(string statusMessages);
}
public class StateMessageService : IStateMessageService
{
public string StateMessages { get; private set; }
public event Action OnChange;
public void SetStateMessages(IStatusMessages statusMessages)
{
StateMessages = statusMessages;
NotifyStateChanged();
}
public void ClearStateMessages()
{
StateMessages = null;
NotifyStateChanged();
}
private void NotifyStateChanged() => OnChange?.Invoke();
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 略
services.AddScoped<IStateMessageService, StateMessageService>(); // 追記
// 略
}
}
@inject IStateMessageService StateMessage
@implements IDisposable
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<AlertMessage Message="StateMessage.StateMessages" />
<div class="content px-4 vh-100">
@Body
</div>
</div>
@code
{
protected override void OnInitialized()
{
StateMessage.OnChange += StateHasChanged;
}
public void Dispose()
{
StateMessage.OnChange -= StateHasChanged;
}
}
@inject IStateMessageService StateMessage
@if(!string.IsNullOrEmpty(Message))
{
<div class="alert alert-success alert-dismissible @(IsVisible ? "show " : "")text-left shadow" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close" @onclick="OnClickCloseButton">
<span aria-hidden="true">×</span>
</button>
<p class="m-0">
@Message
</p>
</div>
}
@code {
[Parameter]
public string Message { get; set; } = string.Empty;
public bool IsVisible { get; set; } = true;
private void OnClickCloseButton()
{
IsVisible = false;
Task.Run(() => StateMessage.ClearStateMessages());
}
}
@inject IStateMessageService StateMessage
<button type="button" @onclick="OnClickButton">Show</button>
@code {
public void OnClickButton()
{
StateMessage.SetStateMessages("Hello world!");
}
}
解説
StateMessageService
DIして各コンポーネントで使っていくサービスです。StateMessageService.SetStateMessages
を使い、表示するメッセージを追加します。内部ではメッセージの追加以外に、StateMessageService.OnChange
に登録されているAction
を実行しています。
後々出てきますが、このAction
にStateHasChanged
を追加することで、Message
の内容が変わるたびにDOM更新が走るといった算段です。
Razor Component
フロント側では@inject
を使いIStateMessageService
を受けます。MainLayout.razor
にメッセージ表示用のAlertMessage
を配置することでどの子コンポーネントでStateMessageService.SetStateMessages
を実行してもメッセージが表示されるようにします。
AlertMessage
自体はBootstrapのAlertをラップしているコンポーネントです。Message
に表示したいメッセージを渡せばいい感じに表示してくれます。
AlertMessage.razor
ではクローズボタンが押された時の処理として、OnClickCloseButton
を定義しています。ここでStateMessageService.ClearStateMessages
を実行することで、非表示及びステートの初期化を行っています。
いいところ
ページ遷移をしてもクローズボタンを押下されない限り表示され続けます。使いどころとしてはブラウザ上でのプッシュ通知などでしょうか? 私個人はAPIのレスポンスメッセージを表示させたりしています。
WASMでもServerSideでもどっちでも使えるところも良いですね。
javascriptを1つも書かずにここまでできるBlazorは本当にすごいと思います。革新的ですね。
悪いところ
紹介したコードではメッセージが1つだけしか表示できません。トーストのようにスタックすることができないので、そういった用途には向かないでしょう。
参考
3 Ways to Communicate Between Components in Blazor
https://chrissainty.com/3-ways-to-communicate-between-components-in-blazor/
環境
- Visual Studio 2019 ver 16.4.2
- TargetFramework > netstandard2.0
- LangVersion > 7.3
- RazorLangVersion > 3.0