FEATUREわかったつもりの、その先へ
知る・学ぶ

押すとすぐ変わるのに、保存はまだ?お気に入りで学ぶ楽観的UI

お気に入りの表示がすぐ変わっても、通信が完了したとは限りません。実際の更新処理を模擬通信で動かし、先に表示を変える仕組みと失敗時の戻し方を確かめました。

CONTENTSこの記事の目次7項目

アプリのお気に入りを更新する処理を読むと、通信より先に画面用の一覧を書き換えていました。ボタンがすぐ「保存済み」になっても、サーバーの返事はまだかもしれません。実際の処理を取り出して通信だけを模擬し、成功と失敗で何が違うかを確かめました。 表示を先に変えるなら、失敗したときの戻し方まで考える必要があります。9月22日の単独操作の検証に続き、10月4日には操作を重ねた場合も確かめると、「変更前の一覧へ戻す」だけでは別の成功を消してしまうことが分かりました。

先に変えるのは、画面で使う状態

まずは2026年9月22日に確認した処理と、単独操作の結果から見ていきます。 確認したsetFavoriteは、変更前のお気に入り一覧をpreviousとして残し、追加するアプリを画面用の一覧へ入れてから通信します。削除のときは先に一覧から外します。成功すると見込んで表示を先に変える、楽観的UIの例です。 アプリのお気に入りは会員限定です。今回の確認は本番アカウントのお気に入りを変更せず、会員状態と一覧を用意した検証環境で行いました。最初にワインQだけがある状態から、coffee diaryを追加する作例です。

2026年9月22日の単独操作の作例。実処理に模擬通信を渡した結果で、複数操作が重なったときの安全性まで示す図ではありません。

返事を保留すると、表示が先に変わる

実更新処理と模擬通信で確かめた順番
通信の状態画面用のお気に入り一覧処理の完了
返事をまだ返さない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まで画面に戻りました。一つの失敗を戻したつもりが、その後に成功した別の変更まで取り消していたわけです。 今回も実際の更新処理を取り出し、通信と画面用の状態更新を模擬しています。ブラウザーでの画面遷移、実通信、サーバーへの保存は試していません。本番のアカウントデータも変更していません。

2026年10月4日の追加検証。旧処理とローカルの修正案に同じ模擬通信を与えた比較です。本番への反映やブラウザー上での動作を確認した図ではありません。

戻す範囲を、失敗したアプリだけに絞る

ローカルの修正案では、失敗したアプリのお気に入り登録状態だけを操作前へ戻し、他のアプリの現在の状態を残します。同じ条件で試すと、最後の一覧は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月に全面再検証したものではありません。修正案の本番反映は未確認です。