押すとすぐ変わるのに、保存はまだ?お気に入りで学ぶ楽観的UI
お気に入りの表示がすぐ変わっても、通信が完了したとは限りません。実際の更新処理を模擬通信で動かし、先に表示を変える仕組みと失敗時の戻し方を確かめました。
CONTENTSこの記事の目次7項目
アプリのお気に入りを更新する処理を読むと、通信より先に画面用の一覧を書き換えていました。ボタンがすぐ「保存済み」になっても、サーバーの返事はまだかもしれません。実際の処理を取り出して通信だけを模擬し、成功と失敗で何が違うかを確かめました。 表示を先に変えるなら、失敗したときの戻し方まで考える必要があります。9月22日の単独操作の検証に続き、10月4日には操作を重ねた場合も確かめると、「変更前の一覧へ戻す」だけでは別の成功を消してしまうことが分かりました。
先に変えるのは、画面で使う状態
まずは2026年9月22日に確認した処理と、単独操作の結果から見ていきます。 確認したsetFavoriteは、変更前のお気に入り一覧をpreviousとして残し、追加するアプリを画面用の一覧へ入れてから通信します。削除のときは先に一覧から外します。成功すると見込んで表示を先に変える、楽観的UIの例です。 アプリのお気に入りは会員限定です。今回の確認は本番アカウントのお気に入りを変更せず、会員状態と一覧を用意した検証環境で行いました。最初にワインQだけがある状態から、coffee diaryを追加する作例です。
返事を保留すると、表示が先に変わる
| 通信の状態 | 画面用のお気に入り一覧 | 処理の完了 |
|---|---|---|
| 返事をまだ返さない | coffee diary、ワインQ | 未完了 |
| 成功の返事を返す | coffee diary、ワインQ | 完了 |
| 失敗の返事を返す別試行 | 元のワインQだけへ戻る | エラーを返す |
成功側と失敗側は、同じ初期状態から別々に試しました。失敗では変更前の一覧が復元され、「お気に入りへ追加できませんでした。」というエラーも設定されました。非会員の条件では、一覧変更や通信より前に利用できない旨のエラーになりました。 通信の返事を意図的に保留したので、表示用データが変わった瞬間と、処理が完了した瞬間を分けて観察できました。実ネットワークの速度や、利用者が体感する短縮時間を測ったものではありません。
画面は、保存中の連打も抑える
アプリページのボタンは、保存処理を呼ぶ前にsavingを有効にし、処理が終わるまで無効になります。成功・失敗のメッセージは処理を待ってから設定します。先に状態を見せることと、完了を伝えることを分けているわけです。 「保存済み」というボタンの表示は、画面用の一覧から決まります。そのため、文字だけをサーバー保存の確証として読むことはできません。処理が失敗すれば表示を戻し、エラーを伝える流れまで含めて見る必要があります。
戻せることにも、前提がある
9月22日に確認した処理は、一回の変更の直前に持っていた一覧へ戻していました。このとき試したのは、一回ずつの操作です。別画面からの操作や、複数の要求が重なった場合は未検証でした。 また、通信エラーが起きても、サーバー側で実際に保存されなかったとは限りません。返事だけ届かなかった可能性を考えるなら、サーバーの状態を再取得して確かめる設計も検討対象になります。模擬した失敗に対して画面を戻せることと、サーバーの保存状態を確認できることは、分けて考える必要があります。
操作を重ねると、別の成功まで戻してしまう
2026年10月4日は、二つのアプリのお気に入りを続けて解除する条件を追加しました。初めに「share&do」と「coffee diary」が登録されている状態で、A=share&doの解除への返事を保留します。その間にB=coffee diaryの解除を成功させ、最後にAだけを通信エラーにしました。 旧処理では、Aを始める前の一覧が丸ごと復元され、解除に成功したBまで画面に戻りました。一つの失敗を戻したつもりが、その後に成功した別の変更まで取り消していたわけです。 今回も実際の更新処理を取り出し、通信と画面用の状態更新を模擬しています。ブラウザーでの画面遷移、実通信、サーバーへの保存は試していません。本番のアカウントデータも変更していません。
戻す範囲を、失敗したアプリだけに絞る
ローカルの修正案では、失敗したアプリのお気に入り登録状態だけを操作前へ戻し、他のアプリの現在の状態を残します。同じ条件で試すと、最後の一覧はAだけになり、Bの解除成功が保たれました。 追加操作でも確認しました。空の一覧からAの追加を保留し、Bの追加を成功させてからAを失敗させると、旧処理では一覧が空に戻ります。修正案ではBが残りました。
| 操作の順番 | 旧処理の最後の一覧 | 修正案の最後の一覧 |
|---|---|---|
| Aの解除を保留 → Bの解除成功 → A失敗 | AとB(Bも戻る) | Aだけ(Bの解除を維持) |
| Aの追加を保留 → Bの追加成功 → A失敗 | 空(Bも消える) | Bだけ(Bの追加を維持) |
修正案は、同じアプリの処理中に逆の操作を求められた場合も、成功したことにはせず「更新中」とエラーを返します。この条件では二つ目の通信が始まらないことを確認しました。異なるアプリの操作は、それぞれ進められます。 ただし、これでサーバーの保存状態まで分かるようになったわけではありません。失敗時に戻す範囲を絞っても、「保存は済んだが返事だけ届かなかった」という曖昧さは残ります。今回確認したのはローカルの修正案の限られた条件であり、すべての競合や本番での修正完了を示すものではありません。
楽観ロックとは、扱う問題が違う
楽観的UIは、通信を待つ間に何を表示するかという工夫です。一方、更新の競合を検出する楽観ロックは、同じデータへ重なった変更をどう扱うかという仕組みです。名前に同じ言葉が入っていても、互いの代わりにはなりません。 このお気に入りの例なら「表示を先に変え、失敗したらその操作の影響を戻して知らせる。ただし、別の成功を巻き戻さず、保存結果が不明な場合も考える」と説明できます。反応の速さだけでなく、返事を待っている間と失敗した後まで追うと、楽観的UIの意味が具体的になります。
READ MORE更新の競合を扱う楽観ロックの記事同じTODOを同時編集したとき、版番号で競合を見つける仕組みを紹介します。→確認日:2026年9月22日(単独操作の検証)、2026年10月4日(操作を重ねた模擬検証とローカル修正案の比較)。9月の画面確認を10月に全面再検証したものではありません。修正案の本番反映は未確認です。