「前の記事」「次の記事」のリンクを追加してみた
先日、Blazor WebAssembly で Advent Calendar アプリを作る記事が Blazor Advent Calendar 2024 に投稿されました。
これをちょっと改造しまして、以下のように、「前の記事」「次の記事」のリンクを、記事ページの右上に設けてみました。
実装は次のようにしました。
まず、記事ページの Razor コンポーネント中、@code
ブロック内に、「前の記事」「次の記事」それぞれの URL を保持するフィールド変数を追加します。
...
@code {
...
private string? prevPostUrl;
private string? nextPostUrl;
...
そして、OnInitializedAsync
ライフサイクルメソッド内で、URL パラメータに渡された日付を元に、「前の記事」「次の記事」それぞれの URL を生成して、先のフィールド変数に格納します。
...
@code {
...
protected override async Task OnInitializedAsync()
{
// 前後の記事へのリンクを生成
prevPostUrl = Day > 1 ? $"/post/{Day - 1}" : null;
nextPostUrl = Day < 25 ? $"/post/{Day + 1}" : null;
...
あとは、これら「前の記事」「次の記事」それぞれの URL を保持するフィールド変数を、<a>
要素の href
属性にバインドして完成です。
...
<SectionContent SectionName="AppBar">
<div class="quick-nav-link">
<a href="@prevPostUrl">前の記事</a>
<span>|</span>
<a href="@nextPostUrl">次の記事</a>
</div>
</SectionContent>
...
表示が更新されない??
早速に動作確認してみます。1日目の記事 (/post/1) を開いた状態で、「次の記事」のリンク (/post/2) をクリックしてみると...
おやおや、ブラウザのアドレスバーに表示されている URL は、ちゃんと 2 日目の記事を指しているにもかかわらず、記事ページ本体は 1 日目の記事を表示したまま変わりません。 開発者コンソールなどで確認するもエラーは起きていないようです。しかも、ページを再読み込みすると 2 日目の記事がちゃんと表示されます。いったい何がいけないのでしょうか。
URL パラメータが変わるだけなら Razor コンポーネントは作り直されない
実は Blazor では、URL パラメータ違いの同じルート URL に遷移した場合、そのルート URL にマッチした Razor コンポーネントのインスタンスは作り直されないのです。代わりに、その Razor コンポーネントのインスタンスの、URL パラメータをバインドしているプロパティ値が書き換わるだけなのです。
例えば、"/" から "/post/1" にページ遷移して、記事ページがはじめて表示されるとき、そのタイミングで Post
Razor コンポーネントのインスタンスが生成され、Day
プロパティには 1 が設定されます。
@page "/post/{day:int}"
...
@code {
...
[Parameter]
public int Day { get; set; } // 👈 1 が格納される
...
その上で、OnInitializedAsync
をはじめとしたライフサイクルメソッドが次々と呼び出され、DOM ツリーへの描画が実施されます。
さてここで、URL "/post/1" から "/post/2" に遷移するときなのですが、このときは、記事ページをレンダリングした Post
Razor コンポーネントのインスタンスは同じインスタンスが使われたまま、Day
プロパティが 2 に設定された上で、再描画が行なわれます。
しかしこのとき、OnInitializedAsync
は呼び出されません。 何となれば、その Razor コンポーネントのインスタンスは既に "初期化済み" だから、です。
@page "/post/{day:int}"
...
@code {
...
[Parameter]
public int Day { get; set; } // 👈 2 が格納される...が、
// 👇 OnInitializedAsync は呼ばれない!
protected override async Task OnInitializedAsync() {
// (ここで記事ソースの取得や整形などを行なっている)
...
これが「前の記事」「次の記事」リンクをクリックしても、記事の表示が変わらない理由です。
ではこのようなシナリオにおいてはどう対処するのがよいのでしょうか。
OnParameterSetAsync
を使おう
Blazor のライフサイクルメソッドのひとつに、OnParameterSetAsync
というメソッドがあります。これは、その Razor コンポーネントのパラメータが設定されるときに呼び出されます。
つまり、URL "/post/" から "/post/2" への遷移によって、記事ページコンポーネントの
Dayプロパティが 1 から 2 に設定されるときにも、この
OnParameterSetAsync` ライフサイクルメソッドが呼び出されます。
ですので、これまで OnInitalizedAsync
で行なってきた、記事ソースの取得や整形などの処理を、OnParameterSetAsync
で行なうようにプログラムを修正するとよいです。
@page "/post/{day:int}"
...
@code {
...
[Parameter]
public int Day { get; set; } // 👈 2 が格納されるときに...
// 👇 OnParametersSetAsync は呼ばれる!
protected override async Task OnParametersSetAsync() {
await base.OnParametersSetAsync();
...
なお、基底クラスの OnParametersSetAsync
を呼び出したあとでないと、[Parameter]
属性を付与したプロパティへのパラメータ値の設定・反映は行なわれませんので、その点はご注意ください。
これで解決です。ちゃんと「前の記事」「次の記事」のリンクが動作するようになりました。
なお今回は変動しうるパラメータが Day
プロパティのみということもあって、雑に、OnIntializedAsync
で行なっていた処理をまるっと OnParameterSetAsync
に引っ越しして済ませましたが、本来は、どのパラメータに変更があったのかとか、そのパラメータ値の変更によってどんな処理を行なうか、など、よく検討の上、適切に実装する必要があるはずです。場合によっては、ShouldRender
ライフサイクルメソッドもオーバーライドして、再描画回数の低減を行なう必要も出てくるかもしれません。
以上、おつかれさまでした。