前回のレクチャーでは、ProtectedSessionStorageを使ってCounter画面のcurrentCountの値をブラウザーが開いている間保持できるようにしました。今回はこのstorageから値を取ってくる処理に時間がかかる場合を想定し、データ取得中であることを画面に示しつつ、取得が完了したら結果を表示するという「非同期を想定したUIの制御」を行います。
図1 変更前のCounter.razor。OnInitializedAsync内でstorage.GetAsyncによりcurrentCountを取得し、IncrementCountでボタン押下時に値を更新・保存する処理がすでに実装されている
1. わざと時間のかかる処理にする
まず、storageから値を取得する処理が実際に時間がかかった場合を想定するため、OnInitializedAsync内にTask.Delay(3000)を追加し、3秒待たせるようにします。
図2 OnInitializedAsync内にTask.Delay(3000)を追加した直後。まだawaitしていないため警告(△マーク)が表示されている
await Task.Delay(3000);
待たせるだけであれば実行結果には反映されないため、awaitを付けて実際に3秒間処理を止めるようにします。
図3 awaitを付けてawait Task.Delay(3000);に修正した状態。警告が消え問題なくビルドできる状態になった
この状態で実行すると、Counter画面を開いた直後はCurrent countが0のまま表示され、3秒経ってからstorageに保存されていた値(この場合は前回保存した値)に置き換わります。値が確定するまでの間も画面には「Current count: 0」とボタンがそのまま表示されてしまうため、クリックのタイミングによっては数値が意図せず変わってしまうなど、わかりにくい動きになってしまいます。
図4 Counter画面に遷移した直後の状態。実際には3秒後にstorageの値へ切り替わるが、画面上は変化がわからない
図5 ボタンをクリックして値を増やした状態。この後Homeへ戻り再度Counterへ遷移すると、3秒間は古い表示のままになってしまう
図6 Home→Counterと遷移した直後の画面。storageから読み込んだ本来の値が反映されるまでの3秒間、見た目上は何が起きているかわからない
2. currentCountの初期値をnullにする
データ取得中かどうかを画面側で判定できるようにするため、currentCountの型をintからint?(null許容型)に変更し、初期値をnull(未設定)にします。
図8 private int? currentCount = null;に変更した状態。IntelliSenseの候補からnullを選択しているところ
private int? currentCount = null;
3. @ifでデータ取得中の表示を切り替える
次に、currentCountの値を表示しているpタグとボタンをまとめて@ifで囲みます。
図7 currentCountを表示するpタグとボタンを@if() { … }で囲んだ状態(条件式はまだ空)
条件式にはcurrentCount.HasValueを指定します。null許容型の値が設定されているかどうかはHasValueプロパティで判定でき、nullでなければtrue(=データが取得できている状態)になります。
図9 @if (currentCount.HasValue)として、値が設定されているときだけCurrent countとボタンを表示するようにした
else側には、まだ値が取得できていないときの表示としてpタグで「データ取得中…」を追加します。
図10 elseブロックに<p>データ取得中…</p>を追加している最中。IntelliSenseのタグ候補が表示されている
@if (currentCount.HasValue)
{
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
}
else
{
<p>データ取得中...</p>
}
4. 取得失敗時にcurrentCountへ0を入れる
currentCountの初期値をnullにしたことで、storageCurrentCount.Successがfalseだった場合(保存された値がまだない場合など)に、currentCountがnullのままになってしまいます。これでは@ifの条件がいつまでもfalseのままになり、「データ取得中…」の表示から進まなくなってしまうため、Successがfalseのときはelse側でcurrentCountに0を代入するようにします。
図11 OnInitializedAsyncの最終形。if (storageCurrentCount.Success)のelse側でcurrentCount = 0;を追加した
protected override async Task OnInitializedAsync()
{
var storageCurrentCount = await storage.GetAsync<int>("counter_currentCount");
await Task.Delay(3000);
if (storageCurrentCount.Success)
{
currentCount = storageCurrentCount.Value;
}
else
{
currentCount = 0;
}
}
5. 動作確認
実行してCounterを開くと、まず「データ取得中…」とだけ表示され、Current countの表示とボタンは出てきません。
図13 Counter画面に遷移した直後。currentCountがnullのため「データ取得中…」だけが表示され、ボタンも表示されない
3秒経つとcurrentCountに値が入り、@ifの条件がtrueになるためCurrent countとボタンがまとめて表示されます。ボタンを押すとcurrentCountがカウントアップされます。
図14 3秒後、currentCount.HasValueがtrueになりCurrent count: 11とボタンが表示された状態
再度Homeへ戻ってからCounterへ遷移すると、同じように最初は「データ取得中…」が表示され、3秒後に値とボタンが表示されるようになります。
図15 Home→Counterと遷移し直した直後。再びcurrentCountがnullに戻るため「データ取得中…」が表示される
このように、値がnullかどうかを@ifで判定するだけで、「データ取得中はボタンを押せないようにしつつ、その旨をメッセージとして表示する」という制御が実現できます。今回はstorageからの取得を想定してTask.Delayで疑似的に時間をかけましたが、データベースから取得する場合など実際に時間がかかる処理でも同じ考え方で、「データが入るまではこちらの表示、入ったらこちらの表示」という切り替えができます。非同期処理を伴うUIを作る際は、こうした「取得中」の状態を意識して制御するようにしてください。
A01_はじめに
A02_バージョンの確認
A03_プロジェクトの作成
A04_アプリの実行
A05_サンプルプログラムの構成
A06_Razorコンポーネント
A07_Razor構文
A08_@pageでURLを指定する
A09_@rendermode
A10_PageTitle
A11_HTMLの見出し
A12_pタグ
A13_単方向データバインド
A14_単方向データバインドの詳細
A15_Bootstrap
A16_ボタンクリックイベント
A17_イベント引数の受け取り方
A18_ここまでのまとめ
B01_新しいWebページを追加する方法
B02_サイドバーから画面を表示する
B03_サイドバーのアイコンを変更する方法
B04_計測値を表示する
B05_変数に置き換える
B06_最新の値でデータを更新する
B07_@usingで通常のC#コードを呼び出す
B08_画面遷移_aタグ
B09_画面遷移_aタグの見た目をボタンにする
B10_画面遷移_NavigationManagerでの遷移
B11_画面間のパラメータの受け渡し方法
B12_EscapeDataStringでエスケープする
B13_複数のパラメータを渡す
B14_任意のパラメータにする
B15_string以外の型をパラメータで使う方法
B16_DateTime型をパラメータで渡す方法
B17_引数2個のURLも型指定に変更する
B18_パラメータ属性をつけるときの注意点
B19_OnParametersSet
B20_OnInitialized
B21_OnInitializedが2回通る問題の対応
B22_ブラウザが開いている間の値を記憶する方法
B23_値の受け渡しとしてのProtectedSessionStorage
B24_非同期を想定したUIの制御
C01_一覧表示
C02_非同期での一覧表示
C03_table-striped
D01_入力エリア
D02_双方向データバインド
D03_入力データをリアルタイムで通知する方法
D04_placeholder
D05_入力エリアの横幅の調整
D06_テキストチェンジイベントを拾う方法
D07_プルダウン
D08_プルダウンのbootstrap
D09_プルダウンの選択している値をデータバインドで操作する
D10_プルダウンにリストの値を表示する
D11_プルダウンに未選択行を入れる
D12_プルダウンの入力を禁止する
D13_プルダウンのチェンジイベントを取得する
D14_チェックボックス
D15_チェックボックスにラベルを付ける
D16_複数のチェックボックスを並べる
D17_ラジオボタン
D18_ラジオボタンのデータバインド
D19_ラジオボタンのチェンジイベント
D20_ラジオボタンにbootstrapを適応する